@promptdriven/pds 0.1.2 → 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.
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_util_1 = require("node:util");
10
10
  const config_1 = require("../config/config");
11
11
  const errors_1 = require("../errors/errors");
12
+ const repo_url_1 = require("../utils/repo-url");
12
13
  const DEFAULT_LOGIN_SCOPES = [
13
14
  "project:create",
14
15
  "project:read",
@@ -24,7 +25,26 @@ const RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS = 250;
24
25
  const RELEASE_VIDEO_REATTACH_MAX_DELAY_MS = 5_000;
25
26
  const RELEASE_VIDEO_WAIT_MAX_CONFIRMATIONS = 60;
26
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;
27
40
  const PRIVATE_INTERNAL_ERROR_DETAIL_KEY_PATTERN = /^(?:errorName|rootCause|raw(?:Provider)?(?:Payload|Response|Error|Message)?|providerRaw(?:Payload|Response|Error|Message)?)$/i;
41
+ const REFERENCE_LIBRARY_SCOPES = ["user", "workspace", "organization"];
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
+ };
28
48
  async function dispatchCommand(commandPath, commandArgs, context) {
29
49
  const command = commandPath.join(" ");
30
50
  switch (command) {
@@ -75,28 +95,40 @@ async function dispatchCommand(commandPath, commandArgs, context) {
75
95
  "dry-run": "boolean",
76
96
  "force-regenerate": "boolean",
77
97
  wait: "boolean",
78
- "idempotency-key": "string"
98
+ "idempotency-key": "string",
99
+ sections: "string[]",
100
+ files: "string[]",
101
+ segments: "string[]",
102
+ clips: "string[]"
79
103
  });
80
104
  const dryRun = values["dry-run"] === true;
81
- const response = await context.client.runPipeline(compact({
105
+ const wait = !dryRun;
106
+ const requestPayload = compact({
82
107
  projectId: requireProject(context.config),
83
108
  to: optionalString(values.to),
84
109
  stage: optionalString(values.stage),
85
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),
86
115
  dryRun: dryRun || undefined,
87
116
  forceRegenerate: values["force-regenerate"] === true || undefined,
88
117
  wait: values.wait === true || undefined,
89
118
  idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
90
- }));
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
+ }
91
126
  await followAgentRunEvents(response, context);
92
127
  return response;
93
128
  }
94
129
  case "jobs watch": {
95
130
  const values = parse(commandArgs, { "job-id": "string", "run-id": "string" });
96
- return await context.client.watchJob({
97
- jobId: requiredRunOrJobId(values),
98
- onEvent: context.emitJsonlEvent
99
- });
131
+ return await watchJobUntilTerminal(requiredRunOrJobId(values), context);
100
132
  }
101
133
  case "jobs cancel": {
102
134
  const values = parse(commandArgs, { "job-id": "string" });
@@ -121,6 +153,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
121
153
  platform: optionalString(values.platform)
122
154
  }));
123
155
  }
156
+ case "distribution thumbnails upload":
157
+ return await distributionThumbnailsUpload(commandArgs, context);
124
158
  case "distribution connections list": {
125
159
  const values = parse(commandArgs, { platform: "string" });
126
160
  return await context.client.listDistributionConnections(compact({
@@ -149,7 +183,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
149
183
  "idempotency-key": "string"
150
184
  });
151
185
  const dryRun = values["dry-run"] === true;
152
- const response = await context.client.publishDistribution(compact({
186
+ const wait = !dryRun;
187
+ const requestPayload = compact({
153
188
  projectId: requireProject(context.config),
154
189
  platform: optionalString(values.platform),
155
190
  privacy: optionalString(values.privacy),
@@ -160,12 +195,75 @@ async function dispatchCommand(commandPath, commandArgs, context) {
160
195
  auditOverrideRationale: optionalString(values["audit-override-rationale"]),
161
196
  wait: values.wait === true || undefined,
162
197
  idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
163
- }));
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
+ }
164
205
  await followAgentRunEvents(response, context);
165
206
  return response;
166
207
  }
208
+ case "reference-library list": {
209
+ const values = parse(commandArgs, { scope: "string" });
210
+ return await context.client.listReferenceLibraries(compact({
211
+ projectId: context.config.projectId,
212
+ scope: referenceLibraryScope(values.scope)
213
+ }));
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
+ }
230
+ case "reference-library items list": {
231
+ const values = parse(commandArgs, { "library-id": "string" });
232
+ return await context.client.listReferenceLibraryItems(compact({
233
+ projectId: context.config.projectId,
234
+ libraryId: requiredString(values["library-id"], "--library-id")
235
+ }));
236
+ }
237
+ case "reference-library bindings list": {
238
+ ensureNoCommandArgs(commandArgs);
239
+ return await context.client.listReferenceLibraryProjectBindings({
240
+ projectId: requireProject(context.config)
241
+ });
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);
251
+ case "reference-library bindings attach":
252
+ return await referenceLibraryBindingsAttach(commandArgs, context);
253
+ case "reference-library bindings update":
254
+ return await referenceLibraryBindingsUpdate(commandArgs, context);
255
+ case "reference-library bindings detach":
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);
167
263
  case "release-video create":
168
264
  return await releaseVideoCreate(commandArgs, context);
265
+ case "release-video preflight":
266
+ return await releaseVideoPreflight(commandArgs, context);
169
267
  case "release-video status":
170
268
  return await releaseVideoStatus(commandArgs, context);
171
269
  default:
@@ -397,6 +495,366 @@ async function projectResetPipeline(commandArgs, context) {
397
495
  idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key"),
398
496
  }));
399
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
+ }
626
+ async function referenceLibraryBindingsAttach(commandArgs, context) {
627
+ const values = parse(commandArgs, {
628
+ "library-id": "string",
629
+ "item-id": "string",
630
+ "version-id": "string",
631
+ "binding-id": "string",
632
+ "reference-id": "string",
633
+ "target-json": "string",
634
+ "update-policy": "string",
635
+ "expected-reference-fingerprint": "string",
636
+ "idempotency-key": "string"
637
+ });
638
+ return await context.client.attachReferenceLibraryProjectBinding(compact({
639
+ projectId: requireProject(context.config),
640
+ libraryId: requiredString(values["library-id"], "--library-id"),
641
+ itemId: requiredString(values["item-id"], "--item-id"),
642
+ versionId: optionalString(values["version-id"]),
643
+ bindingId: optionalString(values["binding-id"]),
644
+ target: referenceLibraryBindingTarget(values),
645
+ updatePolicy: referenceLibraryUpdatePolicy(values["update-policy"]),
646
+ expectedReferenceFingerprint: optionalString(values["expected-reference-fingerprint"]),
647
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
648
+ }));
649
+ }
650
+ async function referenceLibraryBindingsUpdate(commandArgs, context) {
651
+ const values = parse(commandArgs, {
652
+ "binding-id": "string",
653
+ "to-version-id": "string",
654
+ "dry-run": "boolean",
655
+ "idempotency-key": "string"
656
+ });
657
+ const dryRun = values["dry-run"] === true;
658
+ return await context.client.updateReferenceLibraryProjectBinding(compact({
659
+ projectId: requireProject(context.config),
660
+ bindingId: requiredString(values["binding-id"], "--binding-id"),
661
+ toVersionId: optionalString(values["to-version-id"]),
662
+ dryRun: dryRun || undefined,
663
+ idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
664
+ }));
665
+ }
666
+ async function referenceLibraryBindingsDetach(commandArgs, context) {
667
+ const values = parse(commandArgs, {
668
+ "binding-id": "string",
669
+ "delete-materialized-assets": "boolean",
670
+ "idempotency-key": "string"
671
+ });
672
+ return await context.client.detachReferenceLibraryProjectBinding(compact({
673
+ projectId: requireProject(context.config),
674
+ bindingId: requiredString(values["binding-id"], "--binding-id"),
675
+ deleteMaterializedAssets: values["delete-materialized-assets"] === true || undefined,
676
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
677
+ }));
678
+ }
679
+ function referenceLibraryBindingTarget(values) {
680
+ const referenceId = optionalString(values["reference-id"]);
681
+ const targetJson = optionalString(values["target-json"]);
682
+ if (referenceId && targetJson) {
683
+ throw new errors_1.CliError("Use only one of --reference-id or --target-json", {
684
+ code: "invalid_arguments",
685
+ exitCode: errors_1.EXIT_CODES.usage
686
+ });
687
+ }
688
+ if (referenceId) {
689
+ return {
690
+ artifact: "references",
691
+ referenceId
692
+ };
693
+ }
694
+ if (!targetJson) {
695
+ throw new errors_1.CliError("Missing binding target. Pass --reference-id or --target-json.", {
696
+ code: "missing_required_option",
697
+ exitCode: errors_1.EXIT_CODES.usage
698
+ });
699
+ }
700
+ let parsed;
701
+ try {
702
+ parsed = JSON.parse(targetJson);
703
+ }
704
+ catch {
705
+ throw new errors_1.CliError("Invalid --target-json: expected a JSON object", {
706
+ code: "invalid_arguments",
707
+ exitCode: errors_1.EXIT_CODES.usage
708
+ });
709
+ }
710
+ if (!isRecord(parsed)) {
711
+ throw new errors_1.CliError("Invalid --target-json: expected a JSON object", {
712
+ code: "invalid_arguments",
713
+ exitCode: errors_1.EXIT_CODES.usage
714
+ });
715
+ }
716
+ return validateReferenceLibraryTargetJson(parsed);
717
+ }
718
+ function referenceLibraryScope(value) {
719
+ const scope = optionalString(value);
720
+ if (!scope) {
721
+ return undefined;
722
+ }
723
+ if (REFERENCE_LIBRARY_SCOPES.includes(scope)) {
724
+ return scope;
725
+ }
726
+ throw new errors_1.CliError(`Unsupported reference library scope: ${scope}`, {
727
+ code: "invalid_arguments",
728
+ exitCode: errors_1.EXIT_CODES.usage
729
+ });
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
+ }
761
+ function referenceLibraryUpdatePolicy(value) {
762
+ const updatePolicy = optionalString(value);
763
+ if (!updatePolicy) {
764
+ return undefined;
765
+ }
766
+ if (REFERENCE_LIBRARY_UPDATE_POLICIES.includes(updatePolicy)) {
767
+ return updatePolicy;
768
+ }
769
+ throw new errors_1.CliError(`Unsupported --update-policy: ${updatePolicy}`, {
770
+ code: "invalid_arguments",
771
+ exitCode: errors_1.EXIT_CODES.usage
772
+ });
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
+ }
804
+ function validateReferenceLibraryTargetJson(target) {
805
+ const artifact = optionalString(target.artifact);
806
+ if (!artifact) {
807
+ throw new errors_1.CliError('Invalid --target-json: missing "artifact"', {
808
+ code: "invalid_arguments",
809
+ exitCode: errors_1.EXIT_CODES.usage
810
+ });
811
+ }
812
+ if (artifact === "references") {
813
+ requireTargetString(target, "referenceId", artifact);
814
+ assertReferenceLibraryTargetKeys(target, artifact, ["artifact", "referenceId"]);
815
+ return target;
816
+ }
817
+ if (artifact === "style-guide") {
818
+ requireTargetString(target, "styleGuideId", artifact);
819
+ if (target.variantId !== undefined) {
820
+ requireTargetString(target, "variantId", artifact);
821
+ }
822
+ assertReferenceLibraryTargetKeys(target, artifact, ["artifact", "styleGuideId", "variantId"]);
823
+ return target;
824
+ }
825
+ if (artifact === "distribution") {
826
+ if (target.platform !== undefined) {
827
+ requireTargetString(target, "platform", artifact);
828
+ }
829
+ assertReferenceLibraryTargetKeys(target, artifact, ["artifact", "platform"]);
830
+ return target;
831
+ }
832
+ throw new errors_1.CliError(`Invalid --target-json: unsupported artifact "${artifact}"`, {
833
+ code: "invalid_arguments",
834
+ exitCode: errors_1.EXIT_CODES.usage
835
+ });
836
+ }
837
+ function requireTargetString(target, key, artifact) {
838
+ const value = optionalString(target[key]);
839
+ if (!value) {
840
+ throw new errors_1.CliError(`Invalid --target-json: ${artifact}.${key} is required`, {
841
+ code: "invalid_arguments",
842
+ exitCode: errors_1.EXIT_CODES.usage
843
+ });
844
+ }
845
+ return value;
846
+ }
847
+ function assertReferenceLibraryTargetKeys(target, artifact, allowedKeys) {
848
+ const allowed = new Set(allowedKeys);
849
+ for (const key of Object.keys(target)) {
850
+ if (!allowed.has(key)) {
851
+ throw new errors_1.CliError(`Invalid --target-json: unsupported ${artifact}.${key}`, {
852
+ code: "invalid_arguments",
853
+ exitCode: errors_1.EXIT_CODES.usage
854
+ });
855
+ }
856
+ }
857
+ }
400
858
  async function releaseVideoCreate(commandArgs, context) {
401
859
  const values = parse(commandArgs, {
402
860
  "project-name": "string",
@@ -407,6 +865,7 @@ async function releaseVideoCreate(commandArgs, context) {
407
865
  "repo-name": "string",
408
866
  "git-sha": "string",
409
867
  "release-tag": "string",
868
+ "bootstrap-selected-project": "boolean",
410
869
  preset: "string",
411
870
  target: "string",
412
871
  platform: "string",
@@ -430,10 +889,11 @@ async function releaseVideoCreate(commandArgs, context) {
430
889
  script,
431
890
  releaseNotes,
432
891
  changelog,
433
- repoUrl: optionalString(values["repo-url"]),
892
+ repoUrl: (0, repo_url_1.canonicalizeRepoUrl)(optionalString(values["repo-url"])),
434
893
  repoName: optionalString(values["repo-name"]),
435
894
  gitSha: optionalString(values["git-sha"]),
436
895
  releaseTag: optionalString(values["release-tag"]),
896
+ bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
437
897
  preset: optionalString(values.preset),
438
898
  target: optionalString(values.target),
439
899
  platform: optionalString(values.platform),
@@ -449,24 +909,94 @@ async function releaseVideoCreate(commandArgs, context) {
449
909
  ? await createReleaseVideoWithReattach(requestPayload, context)
450
910
  : await context.client.createReleaseVideo(requestPayload);
451
911
  if (wait && !dryRun) {
912
+ emitReleaseVideoWaitHandle(response, context);
452
913
  return await waitForReleaseVideoRun(response, context);
453
914
  }
454
915
  await followAgentRunEvents(response, context);
455
916
  return response;
456
917
  }
918
+ async function releaseVideoPreflight(commandArgs, context) {
919
+ const values = parse(commandArgs, {
920
+ "project-name": "string",
921
+ script: "string",
922
+ "release-notes": "string",
923
+ changelog: "string",
924
+ "repo-url": "string",
925
+ "repo-name": "string",
926
+ "git-sha": "string",
927
+ "release-tag": "string",
928
+ "bootstrap-selected-project": "boolean",
929
+ preset: "string",
930
+ target: "string",
931
+ platform: "string",
932
+ privacy: "string",
933
+ "force-regenerate": "boolean",
934
+ "override-audit": "boolean",
935
+ "audit-override-rationale": "string",
936
+ "idempotency-key": "string"
937
+ });
938
+ const script = await fileContentRequest(optionalString(values.script));
939
+ const releaseNotes = await fileContentRequest(optionalString(values["release-notes"]));
940
+ const changelog = await fileContentRequest(optionalString(values.changelog));
941
+ return await context.client.preflightReleaseVideo(compact({
942
+ projectName: optionalString(values["project-name"]),
943
+ projectId: context.config.projectIdSource === "flag" || context.config.projectIdSource === "env" ? context.config.projectId : undefined,
944
+ script,
945
+ releaseNotes,
946
+ changelog,
947
+ repoUrl: (0, repo_url_1.canonicalizeRepoUrl)(optionalString(values["repo-url"])),
948
+ repoName: optionalString(values["repo-name"]),
949
+ gitSha: optionalString(values["git-sha"]),
950
+ releaseTag: optionalString(values["release-tag"]),
951
+ bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
952
+ preset: optionalString(values.preset),
953
+ target: optionalString(values.target),
954
+ platform: optionalString(values.platform),
955
+ privacy: optionalString(values.privacy),
956
+ forceRegenerate: values["force-regenerate"] === true || undefined,
957
+ overrideAudit: values["override-audit"] === true || undefined,
958
+ auditOverrideRationale: optionalString(values["audit-override-rationale"]),
959
+ idempotencyKey: optionalString(values["idempotency-key"]),
960
+ wait: true
961
+ }));
962
+ }
457
963
  async function releaseVideoStatus(commandArgs, context) {
458
964
  const values = parse(commandArgs, { "run-id": "string" });
459
965
  const runId = requiredString(values["run-id"], "--run-id");
460
966
  const jobResponse = await context.client.getJob({ jobId: runId });
461
967
  return releaseVideoStatusFromJob(jobResponse, runId);
462
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
+ }
463
991
  async function createReleaseVideoWithReattach(request, context) {
464
992
  let attempt = 0;
465
993
  let delayMs = RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS;
466
994
  let sawDroppedCreate = false;
467
995
  while (true) {
468
996
  try {
469
- return await context.client.createReleaseVideo(request);
997
+ return await context.client.createReleaseVideo(request, {
998
+ timeoutMs: context.config.timeoutMs
999
+ });
470
1000
  }
471
1001
  catch (error) {
472
1002
  if (isRecoverableCreateWaitDrop(error)) {
@@ -523,7 +1053,12 @@ function parse(commandArgs, options) {
523
1053
  args: commandArgs,
524
1054
  allowPositionals: false,
525
1055
  strict: true,
526
- 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
+ ]))
527
1062
  }).values;
528
1063
  }
529
1064
  catch (error) {
@@ -560,6 +1095,19 @@ function requiredRunOrJobId(values) {
560
1095
  function optionalString(value) {
561
1096
  return typeof value === "string" && value.length > 0 ? value : undefined;
562
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
+ }
563
1111
  function mutationIdempotencyKey(value, dryRun) {
564
1112
  if (dryRun) {
565
1113
  return optionalString(value);
@@ -597,28 +1145,349 @@ async function followAgentRunEvents(response, context) {
597
1145
  onEvent: context.emitJsonlEvent
598
1146
  });
599
1147
  }
600
- async function waitForReleaseVideoRun(response, context) {
601
- const runId = agentRunIdFromResponse(response);
602
- if (!runId) {
603
- return response;
604
- }
605
- let confirmations = 0;
606
- const seenEvents = new Set();
607
- const emitRunEvent = context.emitJsonlEvent
608
- ? (event) => {
609
- const eventKey = JSON.stringify(event);
610
- if (seenEvents.has(eventKey)) {
611
- return;
612
- }
613
- context.emitJsonlEvent?.(event);
614
- seenEvents.add(eventKey);
615
- }
616
- : undefined;
1148
+ async function publishDistributionWithReattach(request, context) {
1149
+ let attempt = 0;
1150
+ let delayMs = DISTRIBUTION_PUBLISH_REATTACH_INITIAL_DELAY_MS;
1151
+ let sawDroppedCreate = false;
617
1152
  while (true) {
618
- await context.client.watchJob({
619
- jobId: runId,
620
- onEvent: emitRunEvent
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) {
1199
+ const runId = agentRunIdFromResponse(response);
1200
+ if (!runId) {
1201
+ return response;
1202
+ }
1203
+ if (isTerminalDistributionPublishResponse(response)) {
1204
+ await followAgentRunEvents(response, context);
1205
+ return response;
1206
+ }
1207
+ let confirmations = 0;
1208
+ const seenEvents = new Set();
1209
+ const emitRunEvent = context.emitJsonlEvent
1210
+ ? (event) => {
1211
+ const eventKey = JSON.stringify(event);
1212
+ if (seenEvents.has(eventKey)) {
1213
+ return;
1214
+ }
1215
+ context.emitJsonlEvent?.(event);
1216
+ seenEvents.add(eventKey);
1217
+ }
1218
+ : undefined;
1219
+ while (true) {
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
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
621
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
+ }
622
1491
  const jobResponse = await context.client.getJob({ jobId: runId });
623
1492
  const run = readJobRun(jobResponse);
624
1493
  assertTerminalReleaseVideoRunSucceeded(run, runId);
@@ -630,11 +1499,13 @@ async function waitForReleaseVideoRun(response, context) {
630
1499
  throw new errors_1.CliError("Release video run did not reach a terminal state before wait limit", {
631
1500
  code: "release_video_wait_timeout",
632
1501
  exitCode: errors_1.EXIT_CODES.timeout,
633
- details: {
1502
+ details: compact({
634
1503
  runId,
1504
+ eventsUrl: releaseVideoEventsUrl(response, run),
635
1505
  status: run ? optionalString(run.status) ?? null : null,
1506
+ activeStep: activeStepId(run),
636
1507
  confirmations
637
- }
1508
+ })
638
1509
  });
639
1510
  }
640
1511
  context.emitJsonlEvent?.({
@@ -645,26 +1516,144 @@ async function waitForReleaseVideoRun(response, context) {
645
1516
  status: run ? optionalString(run.status) ?? null : null,
646
1517
  attempt: confirmations + 1
647
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
+ });
648
1527
  await (context.sleep ?? defaultSleep)(RELEASE_VIDEO_WAIT_POLL_MS);
649
1528
  confirmations += 1;
650
1529
  }
651
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
+ }
652
1555
  function agentRunIdFromResponse(value) {
653
1556
  const data = readEnvelopeData(value);
654
1557
  return (optionalString(data.runId) ??
655
1558
  runIdFromNested(data.agentRun) ??
656
1559
  runIdFromNested(data.pipeline, "run"));
657
1560
  }
658
- 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) {
659
1567
  const run = readJobRun(jobResponse);
660
1568
  if (!run || !isRecord(response)) {
661
1569
  return response;
662
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);
663
1617
  const normalizedRun = {
664
1618
  ...run,
665
- runId: optionalString(run.runId) ?? optionalString(run.id)
1619
+ ...(runId ? { runId } : {})
666
1620
  };
667
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
+ };
668
1657
  if (!isRecord(data.pipeline)) {
669
1658
  return response;
670
1659
  }
@@ -787,6 +1776,70 @@ function readJobRun(value) {
787
1776
  }
788
1777
  return null;
789
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
+ }
790
1843
  function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
791
1844
  if (!run) {
792
1845
  return;
@@ -823,6 +1876,42 @@ function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
823
1876
  });
824
1877
  }
825
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
+ }
826
1915
  function isTerminalReleaseVideoRun(run) {
827
1916
  const status = run ? optionalString(run.status) : undefined;
828
1917
  return status === "succeeded" ||
@@ -830,6 +1919,56 @@ function isTerminalReleaseVideoRun(run) {
830
1919
  status === "cancelled" ||
831
1920
  status === "timed_out";
832
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
+ }
833
1972
  function releaseRunFailureMessage(status) {
834
1973
  if (status === "timed_out") {
835
1974
  return "Release video run timed out";
@@ -910,6 +2049,12 @@ function isRecoverableCreateWaitDrop(error) {
910
2049
  }
911
2050
  return error instanceof TypeError || isTransportLikeError(error);
912
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
+ }
913
2058
  function isClientTimeoutDrop(error) {
914
2059
  return error.code === "timeout" &&
915
2060
  error.status === undefined &&