@productmaker/mcp 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/http.js +1513 -1170
  2. package/dist/stdio.js +1491 -1157
  3. package/package.json +2 -2
package/dist/stdio.js CHANGED
@@ -264,7 +264,7 @@ var ApiClient = class {
264
264
  };
265
265
 
266
266
  // src/index.ts
267
- var MCP_VERSION = "3.3.0";
267
+ var MCP_VERSION = "3.4.1";
268
268
 
269
269
  // src/meta-upload-app.ts
270
270
  import { readFileSync } from "fs";
@@ -336,7 +336,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
336
336
  __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
337
337
 
338
338
  // src/tools/tasks.ts
339
- import { z as z48 } from "zod";
339
+ import { z as z49 } from "zod";
340
340
 
341
341
  // src/resolve-image.ts
342
342
  import { lookup as dnsLookup } from "dns/promises";
@@ -1517,6 +1517,42 @@ var VideoTier = z5.preprocess((v) => v === "grok" ? "single_call" : v, z5.enum([
1517
1517
  ]));
1518
1518
  var DEFAULT_VIDEO_TIER = "single_call";
1519
1519
 
1520
+ // ../../packages/types/src/image-provider.ts
1521
+ var GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K = [
1522
+ "4:5",
1523
+ "5:4"
1524
+ ];
1525
+ function gptImage2SupportsTier(aspectRatio, tier) {
1526
+ if (tier === "gpt-image-2:1k") return true;
1527
+ return !GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(aspectRatio);
1528
+ }
1529
+ __name(gptImage2SupportsTier, "gptImage2SupportsTier");
1530
+ var IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE = "image_tier_unsupported_for_ratio";
1531
+ var IMAGE_TIER_LIST = [
1532
+ "gpt-image-2:1k",
1533
+ "gpt-image-2:2k",
1534
+ "gpt-image-2:4k"
1535
+ ];
1536
+ function parseImageTier(imageModel) {
1537
+ const normalized = imageModel.trim().toLowerCase();
1538
+ return IMAGE_TIER_LIST.includes(normalized) ? normalized : null;
1539
+ }
1540
+ __name(parseImageTier, "parseImageTier");
1541
+ function validateImageTierForRatio(aspectRatio, imageModel) {
1542
+ if (!aspectRatio || !imageModel) return null;
1543
+ const tier = parseImageTier(imageModel);
1544
+ if (!tier) return null;
1545
+ const ratio = aspectRatio.trim();
1546
+ if (!GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(ratio)) return null;
1547
+ if (gptImage2SupportsTier(ratio, tier)) return null;
1548
+ return buildImageTierUnsupportedMessage(ratio);
1549
+ }
1550
+ __name(validateImageTierForRatio, "validateImageTierForRatio");
1551
+ function buildImageTierUnsupportedMessage(aspectRatio) {
1552
+ return `El formato ${aspectRatio} solo est\xE1 disponible en calidad est\xE1ndar (1K). Elige 1K, o cambia el formato para usar Premium.`;
1553
+ }
1554
+ __name(buildImageTierUnsupportedMessage, "buildImageTierUnsupportedMessage");
1555
+
1520
1556
  // ../../packages/types/src/angle.ts
1521
1557
  import { z as z6 } from "zod";
1522
1558
  var ImageWinnerSignal = z6.enum([
@@ -2857,6 +2893,19 @@ var IngestOptions = z15.object({
2857
2893
  message
2858
2894
  });
2859
2895
  }
2896
+ const tierMessage = validateImageTierForRatio(data.aspectRatio, data.imageModel);
2897
+ if (tierMessage) {
2898
+ ctx.addIssue({
2899
+ code: z15.ZodIssueCode.custom,
2900
+ path: [
2901
+ "imageModel"
2902
+ ],
2903
+ params: {
2904
+ rule: IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE
2905
+ },
2906
+ message: tierMessage
2907
+ });
2908
+ }
2860
2909
  });
2861
2910
  var IngestRequest = z15.object({
2862
2911
  url: z15.string().url(),
@@ -3401,6 +3450,7 @@ var ShopifyPublicationMode = z20.enum([
3401
3450
  "existing",
3402
3451
  "new"
3403
3452
  ]);
3453
+ var SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH = 160;
3404
3454
  var ShopifyPublishRequest = z20.object({
3405
3455
  shopDomain: z20.string().min(3).max(120).optional(),
3406
3456
  /**
@@ -3419,7 +3469,9 @@ var ShopifyPublishRequest = z20.object({
3419
3469
  */
3420
3470
  publicationMode: ShopifyPublicationMode.default("auto"),
3421
3471
  /** Shopify product GID, numeric id, or admin product URL. Required for existing mode. */
3422
- targetProductId: z20.string().trim().min(1).max(300).optional()
3472
+ targetProductId: z20.string().trim().min(1).max(300).optional(),
3473
+ announcementBarEnabled: z20.boolean().optional(),
3474
+ announcementBarText: z20.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
3423
3475
  }).superRefine((data, ctx) => {
3424
3476
  if (data.publicationMode === "existing" && !data.targetProductId) {
3425
3477
  ctx.addIssue({
@@ -3430,6 +3482,24 @@ var ShopifyPublishRequest = z20.object({
3430
3482
  message: "Selecciona un producto de Shopify."
3431
3483
  });
3432
3484
  }
3485
+ if (data.announcementBarEnabled && !data.announcementBarText) {
3486
+ ctx.addIssue({
3487
+ code: z20.ZodIssueCode.custom,
3488
+ path: [
3489
+ "announcementBarText"
3490
+ ],
3491
+ message: "Escribe el texto del anuncio."
3492
+ });
3493
+ }
3494
+ if (!data.announcementBarEnabled && data.announcementBarText) {
3495
+ ctx.addIssue({
3496
+ code: z20.ZodIssueCode.custom,
3497
+ path: [
3498
+ "announcementBarText"
3499
+ ],
3500
+ message: "Activa el anuncio para usar texto."
3501
+ });
3502
+ }
3433
3503
  });
3434
3504
  var ShopifyPublicationStatus = z20.enum([
3435
3505
  "pending",
@@ -3512,6 +3582,10 @@ var CreateShopifyBillingChargeRequest = z20.object({
3512
3582
  var CancelShopifySubscriptionRequest = z20.object({
3513
3583
  subscriptionId: z20.string().min(1)
3514
3584
  });
3585
+ var ShopifyAnnouncementBarState = z20.object({
3586
+ enabled: z20.boolean(),
3587
+ text: z20.string().max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
3588
+ });
3515
3589
  var LandingHealthResponse = z20.object({
3516
3590
  taskId: z20.string(),
3517
3591
  state: z20.enum([
@@ -3553,9 +3627,20 @@ var ApproveAnglesRequest = z21.object({
3553
3627
  "mixed"
3554
3628
  ]).optional()
3555
3629
  });
3630
+ var ApproveAnglesRefusalReason = z21.enum([
3631
+ "photo_review_pending",
3632
+ "photos_not_ready",
3633
+ "reference_out_of_date"
3634
+ ]);
3556
3635
  var ApproveAnglesResponse = z21.object({
3557
3636
  success: z21.boolean(),
3558
- taskId: z21.string().uuid()
3637
+ taskId: z21.string().uuid(),
3638
+ reason: ApproveAnglesRefusalReason.optional()
3639
+ });
3640
+ var IngestResumeRefusalResponse = z21.object({
3641
+ success: z21.literal(false),
3642
+ taskId: z21.string().uuid(),
3643
+ reason: ApproveAnglesRefusalReason.optional()
3559
3644
  });
3560
3645
  var GenerateSuggestionsResponse = z21.object({
3561
3646
  painPoints: z21.array(z21.string()),
@@ -3671,7 +3756,8 @@ var ApproveVideoAnglesRequest = z21.object({
3671
3756
  });
3672
3757
  var ApproveVideoAnglesResponse = z21.object({
3673
3758
  success: z21.boolean(),
3674
- taskId: z21.string().uuid()
3759
+ taskId: z21.string().uuid(),
3760
+ reason: ApproveAnglesRefusalReason.optional()
3675
3761
  });
3676
3762
  var SaveVideoDraftRequest = z21.object({
3677
3763
  /** Global creative direction for all videos. Not spoken dialogue. */
@@ -3703,7 +3789,8 @@ var ApproveImageAnglesRequest = z21.object({
3703
3789
  });
3704
3790
  var ApproveImageAnglesResponse = z21.object({
3705
3791
  success: z21.boolean(),
3706
- taskId: z21.string().uuid()
3792
+ taskId: z21.string().uuid(),
3793
+ reason: ApproveAnglesRefusalReason.optional()
3707
3794
  });
3708
3795
  var SaveImageDraftRequest = z21.object({
3709
3796
  customAngle: z21.string().max(480).optional(),
@@ -4060,6 +4147,10 @@ var MVPResult = z24.object({
4060
4147
  videoAngleCount: z24.number().int().min(1).max(3).optional(),
4061
4148
  adCreative: AdCreative.optional(),
4062
4149
  shopifyPageUrl: z24.string().url().optional(),
4150
+ /** What the announcement bar looks like ON THE PUBLISHED landing right now.
4151
+ * The publish dialog prefills from this: without it the checkbox always opens
4152
+ * unchecked, and republishing anything would read as "remove the bar". */
4153
+ shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
4063
4154
  // Pain points and benefits for image wizard pre-filling
4064
4155
  painPoints: z24.string().optional(),
4065
4156
  benefits: z24.string().optional(),
@@ -4185,7 +4276,19 @@ var StatusResponse = z24.object({
4185
4276
  "done",
4186
4277
  "error"
4187
4278
  ]),
4279
+ /** ENGINEERING string (`Task.error`). Never render this to a seller. */
4188
4280
  error: z24.string().optional(),
4281
+ /**
4282
+ * SELLER-FACING failure copy, in Spanish, already decided by the backend —
4283
+ * render it VERBATIM. Present only for `status: 'error'`.
4284
+ *
4285
+ * Clients used to receive only `error` and guess at it with a local regex
4286
+ * table; measured 2026-08-18 over 187 failed tasks in 60 days, that guess
4287
+ * produced contentless copy ("Error desconocido." / "Error interno") for 123
4288
+ * of them. Populated from `Task.userMessage`, falling back to the same
4289
+ * resolver for rows written before that column existed.
4290
+ */
4291
+ userMessage: z24.string().optional(),
4189
4292
  startedAt: z24.string().datetime().optional(),
4190
4293
  updatedAt: z24.string().datetime().optional(),
4191
4294
  stageStatuses: StageStatusSnapshot,
@@ -4196,6 +4299,10 @@ var StatusResponse = z24.object({
4196
4299
  * campaign target geo when creating Meta ads from this task. */
4197
4300
  targetCountry: z24.string().optional(),
4198
4301
  shopifyPageUrl: z24.string().url().optional(),
4302
+ /** What the announcement bar looks like ON THE PUBLISHED landing right now.
4303
+ * The publish dialog prefills from this: without it the checkbox always opens
4304
+ * unchecked, and republishing anything would read as "remove the bar". */
4305
+ shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
4199
4306
  shopifyShops: z24.array(z24.object({
4200
4307
  shopDomain: z24.string(),
4201
4308
  primaryDomain: z24.string().nullable()
@@ -4206,6 +4313,10 @@ var StatusResponse = z24.object({
4206
4313
  requiresAction: z24.boolean().optional(),
4207
4314
  /** True when the task needs the user's attention rather than passive progress. */
4208
4315
  needsReview: z24.boolean().optional(),
4316
+ /** True when an open gate exists that somebody can actually ANSWER. A strictly
4317
+ * narrower fact than `needsReview` — see the identical field on `TaskSummary`
4318
+ * below for why conflating the two produced a call to action that led nowhere. */
4319
+ hasAnswerableGate: z24.boolean().optional(),
4209
4320
  /** Which landing pipeline produced this task's landing. 'code' tasks have no
4210
4321
  * classic LandingImage rows — the result lives in the inline code editor. */
4211
4322
  landingMode: LandingModeSchema.optional(),
@@ -4276,6 +4387,27 @@ var TaskSummary = z24.object({
4276
4387
  /** True when user action is ACTUALLY needed (angle review or script review ready).
4277
4388
  * Use this for status display to show "Revisar" vs "Progreso". */
4278
4389
  needsReview: z24.boolean().optional(),
4390
+ /**
4391
+ * True when an open gate exists that somebody can actually ANSWER.
4392
+ *
4393
+ * DIFFERENT FACT from `needsReview`, and the difference is the bug. `needsReview`
4394
+ * means "this task is waiting on the seller"; the list badge renders «Revisar»
4395
+ * from it. But a task whose dispatch was lost is also "waiting", with no question
4396
+ * open anywhere — and so is the autopilot angle wait, whose resume button is
4397
+ * measurably INERT (`resumeAfterAngles` CASes on a flag that is false there, so
4398
+ * it returns 200 and dispatches nothing). Both render «Revisar» today and both
4399
+ * lead the seller nowhere. The owner spent hours one night convinced the platform
4400
+ * was frozen, chasing a ghost the badge itself invented.
4401
+ *
4402
+ * Derived from the reconciler's gate registry, NOT from a hand-maintained flag:
4403
+ * being a registry entry is what "answerable" means, because every entry declares
4404
+ * a decider and a deadline at compile time. A gate added tomorrow reports itself
4405
+ * here on its own. See `answerableGateIdsFor` in
4406
+ * apps/api/src/modules/cron/gate-reconciler/gate-registry.ts.
4407
+ *
4408
+ * Render «Revisar» on THIS, not on `needsReview`.
4409
+ */
4410
+ hasAnswerableGate: z24.boolean().optional(),
4279
4411
  createdAt: z24.string().datetime().optional(),
4280
4412
  startedAt: z24.string().datetime().optional(),
4281
4413
  updatedAt: z24.string().datetime().optional(),
@@ -6067,6 +6199,10 @@ var TikTokCreateCampaignResponseSchema = z35.object({
6067
6199
  adsManagerUrl: z35.string().optional()
6068
6200
  });
6069
6201
 
6202
+ // ../../packages/types/src/pricing-gemini-omni.ts
6203
+ var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
6204
+ var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
6205
+
6070
6206
  // ../../packages/types/src/pricing.ts
6071
6207
  var CREDITS_PER_USD_CENT = 10;
6072
6208
  var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
@@ -6475,6 +6611,18 @@ var DEFAULT_PRICING = {
6475
6611
  ourCostCents: 120,
6476
6612
  userPriceCents: 240
6477
6613
  },
6614
+ // Gemini Omni Video (gemini-omni-video) — Mix primary generator since
6615
+ // 2026-08-18. FLAT per generation: 168 KIE credits = $0.84, identical at
6616
+ // 720p and 1080p and independent of the 3.5-10s trim window. Do NOT add
6617
+ // per-second overrides for this key — resolveSuccessBilling bills it flat.
6618
+ //
6619
+ // Shares the constants with the pre-submit ESTIMATOR (mix-cost.ts) rather
6620
+ // than repeating the numbers: a literal here that drifted from the estimator
6621
+ // would quote one price and charge another on every mix.
6622
+ "kie:gemini-omni-video": {
6623
+ ourCostCents: GEMINI_OMNI_FLAT_OUR_COST_CENTS,
6624
+ userPriceCents: GEMINI_OMNI_FLAT_USER_PRICE_CENTS
6625
+ },
6478
6626
  "kie:video-lipsync-generation": {
6479
6627
  ourCostCents: 45,
6480
6628
  userPriceCents: 90
@@ -8438,7 +8586,57 @@ var FeatureSpec = z36.object({
8438
8586
  });
8439
8587
 
8440
8588
  // ../../packages/types/src/mix.ts
8589
+ import { z as z38 } from "zod";
8590
+
8591
+ // ../../packages/types/src/mix-degradation.ts
8441
8592
  import { z as z37 } from "zod";
8593
+ var MIX_DEGRADATION_REASONS = [
8594
+ /**
8595
+ * At least one scene the mix PLANNED is missing from the delivered video, so
8596
+ * the result is shorter than the source. Set at assemble time by comparing
8597
+ * the concat set against the planned scene set — the scene's own code (if it
8598
+ * had one) never reaches the mix-level rollup, which only sees scenes that
8599
+ * SUCCEEDED, and that is exactly how a live 17.00s-of-20.00s delivery
8600
+ * shipped with degradedReason NULL and no banner at all.
8601
+ *
8602
+ * ORDERING: FIRST, above every refusal code. A short delivery must never be
8603
+ * MASKED. It used to sit below the three content refusals on the theory that
8604
+ * a refusal names something the user can change AND explains the missing
8605
+ * scene — but it does neither reliably: in a 3-scene mix where scene 1 fails
8606
+ * outright and scene 2 ships through Wan after a reference refusal, the
8607
+ * refusal copy reads "we used a lower-quality alternative", a claim about
8608
+ * QUALITY that says nothing about the video being shorter than what was paid
8609
+ * for, and that is actively wrong about the dropped scene (for which we used
8610
+ * nothing). Every other code in this list describes a scene that DID ship.
8611
+ */
8612
+ "scenes_dropped",
8613
+ /** Google's safety filter rejected the user's reference photo. Deterministic. */
8614
+ "reference_rejected",
8615
+ /** Google refused the wording of the change description. */
8616
+ "prompt_rejected",
8617
+ /**
8618
+ * The provider refused the content but its message names neither the
8619
+ * reference nor the wording, so we cannot say which. Distinct from
8620
+ * 'provider_unavailable' on purpose: "unavailable" invites the user to retry
8621
+ * unchanged, which can never succeed against a content refusal and only
8622
+ * burns their credits. This code tells them something must change.
8623
+ */
8624
+ "content_rejected",
8625
+ /**
8626
+ * The scene's length fell outside Gemini's usable 3.5s-10s trim window —
8627
+ * EITHER bound. Deliberately ONE code for both: the user controls neither
8628
+ * (scene segmentation is entirely ours), so "too short" and "too long" are
8629
+ * not differently actionable, and one honest explanation beats two that
8630
+ * both amount to "our segmentation produced a length the primary generator
8631
+ * cannot take".
8632
+ */
8633
+ "scene_length_unsupported",
8634
+ /** Transient provider failures exhausted the budget. */
8635
+ "provider_unavailable"
8636
+ ];
8637
+ var MixDegradationReasonSchema = z37.enum(MIX_DEGRADATION_REASONS);
8638
+
8639
+ // ../../packages/types/src/mix.ts
8442
8640
  var MEDIA_TYPES = [
8443
8641
  "video",
8444
8642
  "image"
@@ -8476,38 +8674,38 @@ function isPublicHttpsUrl(raw) {
8476
8674
  return true;
8477
8675
  }
8478
8676
  __name(isPublicHttpsUrl, "isPublicHttpsUrl");
8479
- var PublicHttpsUrl = z37.string().url().refine(isPublicHttpsUrl, {
8677
+ var PublicHttpsUrl = z38.string().url().refine(isPublicHttpsUrl, {
8480
8678
  message: "must be a public HTTPS URL"
8481
8679
  });
8482
- var InternalAssetPath = z37.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
8483
- var MixImageUrl = z37.union([
8680
+ var InternalAssetPath = z38.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
8681
+ var MixImageUrl = z38.union([
8484
8682
  PublicHttpsUrl,
8485
8683
  InternalAssetPath
8486
8684
  ]);
8487
- var MixAxes = z37.object({
8488
- product: z37.boolean(),
8489
- person: z37.boolean(),
8490
- setting: z37.boolean()
8685
+ var MixAxes = z38.object({
8686
+ product: z38.boolean(),
8687
+ person: z38.boolean(),
8688
+ setting: z38.boolean()
8491
8689
  });
8492
- var MixReferences = z37.object({
8493
- product: z37.array(PublicHttpsUrl).max(3, "at most 3 product references"),
8494
- person: z37.array(PublicHttpsUrl).max(3, "at most 3 person references"),
8495
- setting: z37.array(PublicHttpsUrl).max(3, "at most 3 setting references")
8690
+ var MixReferences = z38.object({
8691
+ product: z38.array(PublicHttpsUrl).max(3, "at most 3 product references"),
8692
+ person: z38.array(PublicHttpsUrl).max(3, "at most 3 person references"),
8693
+ setting: z38.array(PublicHttpsUrl).max(3, "at most 3 setting references")
8496
8694
  });
8497
- var MixImageEdit = z37.object({
8498
- instruction: z37.string().min(1).max(600),
8499
- imageUrls: z37.array(MixImageUrl).min(1).max(3)
8695
+ var MixImageEdit = z38.object({
8696
+ instruction: z38.string().min(1).max(600),
8697
+ imageUrls: z38.array(MixImageUrl).min(1).max(3)
8500
8698
  });
8501
- var MixRequest = z37.object({
8502
- mediaType: z37.enum(MEDIA_TYPES),
8503
- originalUrl: z37.union([
8699
+ var MixRequest = z38.object({
8700
+ mediaType: z38.enum(MEDIA_TYPES),
8701
+ originalUrl: z38.union([
8504
8702
  PublicHttpsUrl,
8505
8703
  InternalAssetPath
8506
8704
  ]),
8507
8705
  axes: MixAxes,
8508
8706
  references: MixReferences,
8509
- personText: z37.string().max(500).optional(),
8510
- settingText: z37.string().max(500).optional(),
8707
+ personText: z38.string().max(500).optional(),
8708
+ settingText: z38.string().max(500).optional(),
8511
8709
  /**
8512
8710
  * Optional public URL of a user-uploaded audio file (mp3/wav). Wan Video
8513
8711
  * Edit has no audio input and the mix never lipsyncs: the Wan output is
@@ -8516,11 +8714,11 @@ var MixRequest = z37.object({
8516
8714
  * (mute + overlay). No per-scene slicing, no `reference_audio_urls`.
8517
8715
  */
8518
8716
  audioUrl: PublicHttpsUrl.optional(),
8519
- resolution: z37.enum(RESOLUTIONS).default("720p"),
8520
- originalDurationSec: z37.number().positive().max(600).optional(),
8717
+ resolution: z38.enum(RESOLUTIONS).default("720p"),
8718
+ originalDurationSec: z38.number().positive().max(600).optional(),
8521
8719
  image: MixImageEdit.optional(),
8522
- sourceTaskId: z37.string().max(64).optional(),
8523
- sourceCreativeId: z37.string().max(64).optional()
8720
+ sourceTaskId: z38.string().max(64).optional(),
8721
+ sourceCreativeId: z38.string().max(64).optional()
8524
8722
  }).superRefine((req, ctx) => {
8525
8723
  if (req.mediaType === "image") {
8526
8724
  if (!req.image) {
@@ -8554,18 +8752,18 @@ var MixRequest = z37.object({
8554
8752
  });
8555
8753
  }
8556
8754
  });
8557
- var ReferenceMap = z37.object({
8558
- product: z37.object({
8559
- start: z37.number().int(),
8560
- end: z37.number().int()
8755
+ var ReferenceMap = z38.object({
8756
+ product: z38.object({
8757
+ start: z38.number().int(),
8758
+ end: z38.number().int()
8561
8759
  }).optional(),
8562
- person: z37.object({
8563
- start: z37.number().int(),
8564
- end: z37.number().int()
8760
+ person: z38.object({
8761
+ start: z38.number().int(),
8762
+ end: z38.number().int()
8565
8763
  }).optional(),
8566
- setting: z37.object({
8567
- start: z37.number().int(),
8568
- end: z37.number().int()
8764
+ setting: z38.object({
8765
+ start: z38.number().int(),
8766
+ end: z38.number().int()
8569
8767
  }).optional()
8570
8768
  });
8571
8769
  var SCENE_CLIP_TYPES = [
@@ -8581,39 +8779,39 @@ var INSTANCE_CARDINALITIES = [
8581
8779
  "multiple_same",
8582
8780
  "multiple_distinct"
8583
8781
  ];
8584
- var SceneSegment = z37.object({
8585
- index: z37.number().int().positive(),
8586
- startTs: z37.number().nonnegative(),
8587
- endTs: z37.number().positive(),
8588
- seedanceDuration: z37.number().int().min(4).max(15),
8782
+ var SceneSegment = z38.object({
8783
+ index: z38.number().int().positive(),
8784
+ startTs: z38.number().nonnegative(),
8785
+ endTs: z38.number().positive(),
8786
+ seedanceDuration: z38.number().int().min(4).max(15),
8589
8787
  // Pass 1 / Pass 2 outputs (all optional — populated when the rich analyzer
8590
8788
  // runs and empty on fallback).
8591
- action: z37.string().optional(),
8592
- motion: z37.string().optional(),
8593
- clipType: z37.enum(SCENE_CLIP_TYPES).optional(),
8594
- hasCharacter: z37.boolean().optional(),
8595
- faceVisible: z37.boolean().optional(),
8789
+ action: z38.string().optional(),
8790
+ motion: z38.string().optional(),
8791
+ clipType: z38.enum(SCENE_CLIP_TYPES).optional(),
8792
+ hasCharacter: z38.boolean().optional(),
8793
+ faceVisible: z38.boolean().optional(),
8596
8794
  // Pass 2 refinement — rich per-scene metadata used by buildMixPrompt.
8597
- startState: z37.string().optional(),
8598
- endState: z37.string().optional(),
8599
- cameraMovement: z37.string().optional(),
8600
- emotion: z37.string().optional(),
8601
- productPlacement: z37.string().optional(),
8602
- productInteraction: z37.string().optional(),
8603
- settingDescription: z37.string().optional(),
8604
- dialogue: z37.string().optional(),
8795
+ startState: z38.string().optional(),
8796
+ endState: z38.string().optional(),
8797
+ cameraMovement: z38.string().optional(),
8798
+ emotion: z38.string().optional(),
8799
+ productPlacement: z38.string().optional(),
8800
+ productInteraction: z38.string().optional(),
8801
+ settingDescription: z38.string().optional(),
8802
+ dialogue: z38.string().optional(),
8605
8803
  // Main vs extras / noise (our addition on top of the script's schema).
8606
- mainPersonDescription: z37.string().optional(),
8607
- mainProductDescription: z37.string().optional(),
8608
- personInstances: z37.enum(INSTANCE_CARDINALITIES).optional(),
8609
- productInstances: z37.enum(INSTANCE_CARDINALITIES).optional(),
8804
+ mainPersonDescription: z38.string().optional(),
8805
+ mainProductDescription: z38.string().optional(),
8806
+ personInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
8807
+ productInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
8610
8808
  /**
8611
8809
  * Seconds of prev-clipType content absorbed at the start of this scene by
8612
8810
  * Pass 2.7's clipType boundary margin. When > 0 and the scene is a
8613
8811
  * pass-through (no Seedance), the concatenator prepends a held-still
8614
8812
  * preamble (nano-banana-swapped first frame) to hide the leak.
8615
8813
  */
8616
- absorbedMarginSec: z37.number().nonnegative().optional(),
8814
+ absorbedMarginSec: z38.number().nonnegative().optional(),
8617
8815
  /**
8618
8816
  * True when the scene shows post-production graphics baked into the frame:
8619
8817
  * caption/subtitle text, infographic overlay cards, or inset
@@ -8622,33 +8820,39 @@ var SceneSegment = z37.object({
8622
8820
  * (mentioning nonexistent overlays gives the generator license to
8623
8821
  * fabricate them).
8624
8822
  */
8625
- hasOverlayGraphics: z37.boolean().optional()
8823
+ hasOverlayGraphics: z38.boolean().optional()
8626
8824
  });
8627
- var MixSceneResult = z37.object({
8628
- index: z37.number().int().positive(),
8629
- status: z37.enum([
8825
+ var MixSceneResult = z38.object({
8826
+ index: z38.number().int().positive(),
8827
+ status: z38.enum([
8630
8828
  "queued",
8631
8829
  "submitted",
8632
8830
  "done",
8633
8831
  "failed"
8634
8832
  ]),
8635
- seedanceTaskId: z37.string().optional(),
8636
- videoUrl: z37.string().url().optional(),
8637
- error: z37.string().optional()
8638
- });
8639
- var MixResult = z37.object({
8640
- mixTaskId: z37.string().uuid(),
8641
- status: z37.enum([
8833
+ seedanceTaskId: z38.string().optional(),
8834
+ videoUrl: z38.string().url().optional(),
8835
+ error: z38.string().optional(),
8836
+ degradedReason: MixDegradationReasonSchema.optional()
8837
+ });
8838
+ var MixResult = z38.object({
8839
+ mixTaskId: z38.string().uuid(),
8840
+ status: z38.enum([
8642
8841
  "queued",
8643
8842
  "processing",
8644
8843
  "done",
8645
8844
  "error"
8646
8845
  ]),
8647
- resultUrl: z37.string().url().optional(),
8648
- scenes: z37.array(MixSceneResult).default([]),
8649
- failReason: z37.string().optional()
8846
+ resultUrl: z38.string().url().optional(),
8847
+ scenes: z38.array(MixSceneResult).default([]),
8848
+ failReason: z38.string().optional(),
8849
+ degradedReason: MixDegradationReasonSchema.optional()
8650
8850
  });
8651
8851
 
8852
+ // ../../packages/types/src/mix-cost.ts
8853
+ var MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND = 1;
8854
+ var MAX_BILLABLE_GENERATIONS_PER_BLOCK = 1 + MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND;
8855
+
8652
8856
  // ../../packages/types/src/product-delivery-format.ts
8653
8857
  var DELIVERY_FAMILIES = [
8654
8858
  "solid_dose",
@@ -8737,41 +8941,41 @@ var DECLARATION = DELIVERY_FAMILIES.map((family) => [
8737
8941
  ]);
8738
8942
 
8739
8943
  // ../../packages/types/src/task-type.ts
8740
- import { z as z38 } from "zod";
8741
- var TaskTypeSchema = z38.enum([
8944
+ import { z as z39 } from "zod";
8945
+ var TaskTypeSchema = z39.enum([
8742
8946
  "product",
8743
8947
  "service"
8744
8948
  ]);
8745
8949
 
8746
8950
  // ../../packages/types/src/landing-regeneration.ts
8747
- import { z as z39 } from "zod";
8748
- var LandingRegenerationStatus = z39.enum([
8951
+ import { z as z40 } from "zod";
8952
+ var LandingRegenerationStatus = z40.enum([
8749
8953
  "queued",
8750
8954
  "processing",
8751
8955
  "published",
8752
8956
  "failed"
8753
8957
  ]);
8754
- var RegenerateLandingRoleResponse = z39.object({
8755
- ok: z39.literal(true),
8958
+ var RegenerateLandingRoleResponse = z40.object({
8959
+ ok: z40.literal(true),
8756
8960
  role: LandingRole,
8757
- promptIndex: z39.number().int().nonnegative(),
8758
- operationId: z39.string().min(1)
8961
+ promptIndex: z40.number().int().nonnegative(),
8962
+ operationId: z40.string().min(1)
8759
8963
  });
8760
- var LandingRegenerationStatusResponse = z39.object({
8761
- ok: z39.literal(true),
8762
- operationId: z39.string().min(1),
8964
+ var LandingRegenerationStatusResponse = z40.object({
8965
+ ok: z40.literal(true),
8966
+ operationId: z40.string().min(1),
8763
8967
  role: LandingRole,
8764
8968
  status: LandingRegenerationStatus,
8765
- refunded: z39.boolean(),
8766
- failureReason: z39.string().nullable(),
8767
- publishedAssetKey: z39.string().nullable(),
8768
- updatedAt: z39.string().datetime()
8969
+ refunded: z40.boolean(),
8970
+ failureReason: z40.string().nullable(),
8971
+ publishedAssetKey: z40.string().nullable(),
8972
+ updatedAt: z40.string().datetime()
8769
8973
  });
8770
8974
 
8771
8975
  // ../../packages/types/src/video-edit.ts
8772
- import { z as z40 } from "zod";
8976
+ import { z as z41 } from "zod";
8773
8977
  var MAX_SEGMENT_SECONDS = 3;
8774
- var VideoEditStatusSchema = z40.enum([
8978
+ var VideoEditStatusSchema = z41.enum([
8775
8979
  "pending",
8776
8980
  "processing",
8777
8981
  "assembling",
@@ -8780,9 +8984,9 @@ var VideoEditStatusSchema = z40.enum([
8780
8984
  "discarded",
8781
8985
  "failed"
8782
8986
  ]);
8783
- var VideoEditTimeSectionSchema = z40.object({
8784
- startSec: z40.number().min(0),
8785
- endSec: z40.number().positive()
8987
+ var VideoEditTimeSectionSchema = z41.object({
8988
+ startSec: z41.number().min(0),
8989
+ endSec: z41.number().positive()
8786
8990
  }).refine((v) => v.endSec > v.startSec, {
8787
8991
  message: "endSec must be after startSec",
8788
8992
  path: [
@@ -8794,45 +8998,45 @@ var VideoEditTimeSectionSchema = z40.object({
8794
8998
  "endSec"
8795
8999
  ]
8796
9000
  });
8797
- var VideoEditRequestSchema = z40.object({
8798
- comment: z40.string().trim().min(1).max(2e3),
8799
- sections: z40.array(VideoEditTimeSectionSchema).min(1).optional()
9001
+ var VideoEditRequestSchema = z41.object({
9002
+ comment: z41.string().trim().min(1).max(2e3),
9003
+ sections: z41.array(VideoEditTimeSectionSchema).min(1).optional()
8800
9004
  });
8801
- var VideoEditModeSchema = z40.enum([
9005
+ var VideoEditModeSchema = z41.enum([
8802
9006
  "wan_edit",
8803
9007
  "regenerate"
8804
9008
  ]);
8805
- var VideoRegenerationEngineSchema = z40.enum([
9009
+ var VideoRegenerationEngineSchema = z41.enum([
8806
9010
  "free_text",
8807
9011
  "free_image",
8808
9012
  "single_call",
8809
9013
  "multi_shot"
8810
9014
  ]);
8811
- var VideoRegenerationRouteFingerprintSchema = z40.string().trim().min(1).max(128);
8812
- var RegenerateEditRequestSchema = z40.object({
8813
- angleKey: z40.string().min(1),
8814
- comment: z40.string().min(1).max(2e3)
9015
+ var VideoRegenerationRouteFingerprintSchema = z41.string().trim().min(1).max(128);
9016
+ var RegenerateEditRequestSchema = z41.object({
9017
+ angleKey: z41.string().min(1),
9018
+ comment: z41.string().min(1).max(2e3)
8815
9019
  });
8816
9020
 
8817
9021
  // ../../packages/types/src/uploaded-asset.ts
8818
- import { z as z41 } from "zod";
8819
- var UploadedAssetMediaKind = z41.enum([
9022
+ import { z as z42 } from "zod";
9023
+ var UploadedAssetMediaKind = z42.enum([
8820
9024
  "image",
8821
9025
  "video"
8822
9026
  ]);
8823
- var UploadedAssetSessionMediaKind = z41.enum([
9027
+ var UploadedAssetSessionMediaKind = z42.enum([
8824
9028
  "image",
8825
9029
  "video",
8826
9030
  "mixed"
8827
9031
  ]);
8828
- var UploadedAssetRole = z41.enum([
9032
+ var UploadedAssetRole = z42.enum([
8829
9033
  "media",
8830
9034
  "thumbnail"
8831
9035
  ]);
8832
9036
  var UPLOADED_ASSET_MAX_FILES = 10;
8833
9037
  var UPLOADED_ASSET_IMAGE_MAX_FILES = 5;
8834
- var UploadedAssetId = z41.string().uuid();
8835
- var HttpsUrl = z41.string().url().refine((value) => {
9038
+ var UploadedAssetId = z42.string().uuid();
9039
+ var HttpsUrl = z42.string().url().refine((value) => {
8836
9040
  try {
8837
9041
  const url = new URL(value);
8838
9042
  return url.protocol === "https:" && !url.username && !url.password;
@@ -8840,23 +9044,23 @@ var HttpsUrl = z41.string().url().refine((value) => {
8840
9044
  return false;
8841
9045
  }
8842
9046
  }, "URL must use https");
8843
- var UtcDateTime = z41.string().datetime().refine((value) => {
9047
+ var UtcDateTime = z42.string().datetime().refine((value) => {
8844
9048
  const parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/.exec(value);
8845
9049
  const timestamp = Date.parse(value);
8846
9050
  if (!parts || Number.isNaN(timestamp)) return false;
8847
9051
  const date = new Date(timestamp);
8848
9052
  return date.getUTCFullYear() === Number(parts[1]) && date.getUTCMonth() + 1 === Number(parts[2]) && date.getUTCDate() === Number(parts[3]) && date.getUTCHours() === Number(parts[4]) && date.getUTCMinutes() === Number(parts[5]) && date.getUTCSeconds() === Number(parts[6]);
8849
9053
  }, "datetime must be a valid UTC calendar instant");
8850
- var UploadedAssetRef = z41.object({
9054
+ var UploadedAssetRef = z42.object({
8851
9055
  assetId: UploadedAssetId
8852
9056
  }).strict();
8853
9057
  var UploadedAssetCompletion = UploadedAssetRef.extend({
8854
9058
  mediaKind: UploadedAssetMediaKind,
8855
9059
  assetRole: UploadedAssetRole
8856
9060
  }).strict();
8857
- var UploadedAssetMetadata = z41.object({
9061
+ var UploadedAssetMetadata = z42.object({
8858
9062
  assetId: UploadedAssetId,
8859
- ownerId: z41.string().min(1),
9063
+ ownerId: z42.string().min(1),
8860
9064
  mediaKind: UploadedAssetMediaKind,
8861
9065
  assetRole: UploadedAssetRole,
8862
9066
  createdAt: UtcDateTime,
@@ -8864,7 +9068,7 @@ var UploadedAssetMetadata = z41.object({
8864
9068
  }).strict().superRefine((value, ctx) => {
8865
9069
  if (Date.parse(value.expiresAt) <= Date.parse(value.createdAt)) {
8866
9070
  ctx.addIssue({
8867
- code: z41.ZodIssueCode.custom,
9071
+ code: z42.ZodIssueCode.custom,
8868
9072
  path: [
8869
9073
  "expiresAt"
8870
9074
  ],
@@ -8872,27 +9076,27 @@ var UploadedAssetMetadata = z41.object({
8872
9076
  });
8873
9077
  }
8874
9078
  });
8875
- var MediaSourceRef = z41.object({
9079
+ var MediaSourceRef = z42.object({
8876
9080
  url: HttpsUrl.optional(),
8877
- path: z41.string().min(1).optional(),
9081
+ path: z42.string().min(1).optional(),
8878
9082
  assetId: UploadedAssetId.optional()
8879
9083
  }).strict().superRefine((value, ctx) => {
8880
9084
  const sourceCount = Number(value.url !== void 0) + Number(value.path !== void 0) + Number(value.assetId !== void 0);
8881
9085
  if (sourceCount !== 1) {
8882
9086
  ctx.addIssue({
8883
- code: z41.ZodIssueCode.custom,
9087
+ code: z42.ZodIssueCode.custom,
8884
9088
  message: "media source needs exactly one of url, path, or assetId"
8885
9089
  });
8886
9090
  }
8887
9091
  });
8888
- var UploadedAssetSessionRequest = z41.object({
9092
+ var UploadedAssetSessionRequest = z42.object({
8889
9093
  mediaKind: UploadedAssetSessionMediaKind,
8890
9094
  assetRole: UploadedAssetRole.default("media"),
8891
- maxFiles: z41.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
9095
+ maxFiles: z42.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
8892
9096
  }).strict().superRefine((value, ctx) => {
8893
9097
  if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
8894
9098
  ctx.addIssue({
8895
- code: z41.ZodIssueCode.custom,
9099
+ code: z42.ZodIssueCode.custom,
8896
9100
  path: [
8897
9101
  "mediaKind"
8898
9102
  ],
@@ -8901,7 +9105,7 @@ var UploadedAssetSessionRequest = z41.object({
8901
9105
  }
8902
9106
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
8903
9107
  ctx.addIssue({
8904
- code: z41.ZodIssueCode.custom,
9108
+ code: z42.ZodIssueCode.custom,
8905
9109
  path: [
8906
9110
  "maxFiles"
8907
9111
  ],
@@ -8910,7 +9114,7 @@ var UploadedAssetSessionRequest = z41.object({
8910
9114
  }
8911
9115
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > UPLOADED_ASSET_IMAGE_MAX_FILES) {
8912
9116
  ctx.addIssue({
8913
- code: z41.ZodIssueCode.custom,
9117
+ code: z42.ZodIssueCode.custom,
8914
9118
  path: [
8915
9119
  "maxFiles"
8916
9120
  ],
@@ -8919,7 +9123,7 @@ var UploadedAssetSessionRequest = z41.object({
8919
9123
  }
8920
9124
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
8921
9125
  ctx.addIssue({
8922
- code: z41.ZodIssueCode.custom,
9126
+ code: z42.ZodIssueCode.custom,
8923
9127
  path: [
8924
9128
  "mediaKind"
8925
9129
  ],
@@ -8927,32 +9131,32 @@ var UploadedAssetSessionRequest = z41.object({
8927
9131
  });
8928
9132
  }
8929
9133
  });
8930
- var UploadedAssetSessionStatus = z41.discriminatedUnion("status", [
8931
- z41.object({
8932
- status: z41.literal("pending"),
8933
- assets: z41.array(UploadedAssetCompletion).length(0)
9134
+ var UploadedAssetSessionStatus = z42.discriminatedUnion("status", [
9135
+ z42.object({
9136
+ status: z42.literal("pending"),
9137
+ assets: z42.array(UploadedAssetCompletion).length(0)
8934
9138
  }).strict(),
8935
- z41.object({
8936
- status: z41.literal("uploaded"),
8937
- assets: z41.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
9139
+ z42.object({
9140
+ status: z42.literal("uploaded"),
9141
+ assets: z42.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
8938
9142
  }).strict()
8939
9143
  ]);
8940
- var UploadedAssetAdoptionRequest = z41.object({
9144
+ var UploadedAssetAdoptionRequest = z42.object({
8941
9145
  assetId: UploadedAssetId,
8942
- ownerId: z41.string().min(1),
8943
- consumer: z41.string().min(1).max(100),
8944
- idempotencyKey: z41.string().min(1).max(255)
9146
+ ownerId: z42.string().min(1),
9147
+ consumer: z42.string().min(1).max(100),
9148
+ idempotencyKey: z42.string().min(1).max(255)
8945
9149
  }).strict();
8946
9150
 
8947
9151
  // ../../packages/types/src/dropshipping-project.ts
8948
- import { z as z42 } from "zod";
8949
- var DropshippingProjectStatusSchema = z42.enum([
9152
+ import { z as z43 } from "zod";
9153
+ var DropshippingProjectStatusSchema = z43.enum([
8950
9154
  "active",
8951
9155
  "completed",
8952
9156
  "archived"
8953
9157
  ]);
8954
9158
  var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
8955
- var DropshippingRewardMilestoneSchema = z42.enum([
9159
+ var DropshippingRewardMilestoneSchema = z43.enum([
8956
9160
  "product_selected",
8957
9161
  "costing_saved",
8958
9162
  "product_media_ready",
@@ -8967,21 +9171,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
8967
9171
  campaign_created: 200
8968
9172
  };
8969
9173
  var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
8970
- var DropshippingProjectRewardMilestoneStateSchema = z42.object({
9174
+ var DropshippingProjectRewardMilestoneStateSchema = z43.object({
8971
9175
  milestone: DropshippingRewardMilestoneSchema,
8972
- credits: z42.number().int().positive(),
8973
- available: z42.boolean().optional(),
8974
- earned: z42.boolean(),
8975
- grantId: z42.string().uuid().nullable(),
8976
- grantedAt: z42.string().datetime().nullable()
8977
- });
8978
- var DropshippingProjectRewardProgressSchema = z42.object({
8979
- campaignKey: z42.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
8980
- totalCredits: z42.number().int().positive(),
8981
- earnedCredits: z42.number().int().nonnegative(),
8982
- milestones: z42.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
8983
- });
8984
- var DropshippingProjectPhaseSchema = z42.enum([
9176
+ credits: z43.number().int().positive(),
9177
+ available: z43.boolean().optional(),
9178
+ earned: z43.boolean(),
9179
+ grantId: z43.string().uuid().nullable(),
9180
+ grantedAt: z43.string().datetime().nullable()
9181
+ });
9182
+ var DropshippingProjectRewardProgressSchema = z43.object({
9183
+ campaignKey: z43.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
9184
+ totalCredits: z43.number().int().positive(),
9185
+ earnedCredits: z43.number().int().nonnegative(),
9186
+ milestones: z43.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
9187
+ });
9188
+ var DropshippingProjectPhaseSchema = z43.enum([
8985
9189
  "research",
8986
9190
  "costing",
8987
9191
  "media",
@@ -8990,18 +9194,18 @@ var DropshippingProjectPhaseSchema = z42.enum([
8990
9194
  "advertising",
8991
9195
  "complete"
8992
9196
  ]);
8993
- var DropshippingProjectCostingInputSchema = z42.object({
8994
- country: z42.string().min(2).max(3),
8995
- supplierCost: z42.number().finite().positive(),
8996
- shippingCost: z42.number().finite().nonnegative(),
8997
- returnRate: z42.number().finite().min(0).lt(1),
8998
- cancellationRate: z42.number().finite().min(0).lt(1),
8999
- adsManagerCpa: z42.number().finite().nonnegative(),
9000
- desiredMargin: z42.number().finite().min(0).lt(1)
9197
+ var DropshippingProjectCostingInputSchema = z43.object({
9198
+ country: z43.string().min(2).max(3),
9199
+ supplierCost: z43.number().finite().positive(),
9200
+ shippingCost: z43.number().finite().nonnegative(),
9201
+ returnRate: z43.number().finite().min(0).lt(1),
9202
+ cancellationRate: z43.number().finite().min(0).lt(1),
9203
+ adsManagerCpa: z43.number().finite().nonnegative(),
9204
+ desiredMargin: z43.number().finite().min(0).lt(1)
9001
9205
  }).superRefine(({ returnRate, cancellationRate }, context) => {
9002
9206
  if (returnRate + cancellationRate >= 1) {
9003
9207
  context.addIssue({
9004
- code: z42.ZodIssueCode.custom,
9208
+ code: z43.ZodIssueCode.custom,
9005
9209
  path: [
9006
9210
  "cancellationRate"
9007
9211
  ],
@@ -9009,24 +9213,24 @@ var DropshippingProjectCostingInputSchema = z42.object({
9009
9213
  });
9010
9214
  }
9011
9215
  });
9012
- var DropshippingProjectCostingResultSchema = z42.object({
9013
- country: z42.string().min(2).max(3),
9014
- deliveryRate: z42.number().finite(),
9015
- adjustedShippingCost: z42.number().finite(),
9016
- adjustedCpa: z42.number().finite(),
9017
- totalCost: z42.number().finite(),
9018
- suggestedPrice: z42.number().finite(),
9019
- baseCommercialPrice: z42.number().finite(),
9020
- minimumSafePrice: z42.number().finite(),
9021
- commercialPrice: z42.number().finite(),
9022
- comparisonPrice: z42.number().finite(),
9023
- estimatedProfit: z42.number().finite(),
9024
- actualMargin: z42.number().finite(),
9025
- breakEvenAdsManagerCpa: z42.number().finite(),
9026
- wasPriceAutoAdjusted: z42.boolean(),
9027
- profitabilityStatus: z42.literal("healthy")
9028
- });
9029
- var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
9216
+ var DropshippingProjectCostingResultSchema = z43.object({
9217
+ country: z43.string().min(2).max(3),
9218
+ deliveryRate: z43.number().finite(),
9219
+ adjustedShippingCost: z43.number().finite(),
9220
+ adjustedCpa: z43.number().finite(),
9221
+ totalCost: z43.number().finite(),
9222
+ suggestedPrice: z43.number().finite(),
9223
+ baseCommercialPrice: z43.number().finite(),
9224
+ minimumSafePrice: z43.number().finite(),
9225
+ commercialPrice: z43.number().finite(),
9226
+ comparisonPrice: z43.number().finite(),
9227
+ estimatedProfit: z43.number().finite(),
9228
+ actualMargin: z43.number().finite(),
9229
+ breakEvenAdsManagerCpa: z43.number().finite(),
9230
+ wasPriceAutoAdjusted: z43.boolean(),
9231
+ profitabilityStatus: z43.literal("healthy")
9232
+ });
9233
+ var HttpUrlSchema = z43.string().url().max(2e3).refine((value) => {
9030
9234
  try {
9031
9235
  return [
9032
9236
  "http:",
@@ -9036,124 +9240,124 @@ var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
9036
9240
  return false;
9037
9241
  }
9038
9242
  }, "Product URL must use HTTP(S)");
9039
- var ResearchCandidateOriginSchema = z42.object({
9040
- type: z42.literal("research_candidate"),
9041
- runId: z42.string().uuid(),
9042
- candidateSourceId: z42.string().min(1).max(160)
9043
- });
9044
- var TaskOriginSchema = z42.object({
9045
- type: z42.literal("task"),
9046
- taskId: z42.string().uuid()
9047
- });
9048
- var ManualOriginSchema = z42.object({
9049
- type: z42.literal("manual"),
9050
- clientRequestId: z42.string().uuid(),
9051
- country: z42.string().min(2).max(3).optional()
9052
- });
9053
- var CreateDropshippingProjectRequestSchema = z42.object({
9054
- origin: z42.discriminatedUnion("type", [
9243
+ var ResearchCandidateOriginSchema = z43.object({
9244
+ type: z43.literal("research_candidate"),
9245
+ runId: z43.string().uuid(),
9246
+ candidateSourceId: z43.string().min(1).max(160)
9247
+ });
9248
+ var TaskOriginSchema = z43.object({
9249
+ type: z43.literal("task"),
9250
+ taskId: z43.string().uuid()
9251
+ });
9252
+ var ManualOriginSchema = z43.object({
9253
+ type: z43.literal("manual"),
9254
+ clientRequestId: z43.string().uuid(),
9255
+ country: z43.string().min(2).max(3).optional()
9256
+ });
9257
+ var CreateDropshippingProjectRequestSchema = z43.object({
9258
+ origin: z43.discriminatedUnion("type", [
9055
9259
  ResearchCandidateOriginSchema,
9056
9260
  TaskOriginSchema,
9057
9261
  ManualOriginSchema
9058
9262
  ]),
9059
- productName: z42.string().trim().min(1).max(240).optional(),
9263
+ productName: z43.string().trim().min(1).max(240).optional(),
9060
9264
  productUrl: HttpUrlSchema.optional(),
9061
- country: z42.string().min(2).max(3).optional(),
9062
- languageCode: z42.string().min(2).max(16).optional()
9265
+ country: z43.string().min(2).max(3).optional(),
9266
+ languageCode: z43.string().min(2).max(16).optional()
9063
9267
  }).strict();
9064
- var SaveDropshippingProjectCostingRequestSchema = z42.object({
9268
+ var SaveDropshippingProjectCostingRequestSchema = z43.object({
9065
9269
  input: DropshippingProjectCostingInputSchema,
9066
- version: z42.number().int().positive()
9270
+ version: z43.number().int().positive()
9067
9271
  }).strict();
9068
- var DropshippingProjectNextActionSchema = z42.discriminatedUnion("kind", [
9069
- z42.object({
9070
- kind: z42.literal("cost_product"),
9071
- href: z42.string().min(1)
9272
+ var DropshippingProjectNextActionSchema = z43.discriminatedUnion("kind", [
9273
+ z43.object({
9274
+ kind: z43.literal("cost_product"),
9275
+ href: z43.string().min(1)
9072
9276
  }),
9073
- z42.object({
9074
- kind: z42.literal("provide_product_media"),
9075
- href: z42.string().min(1)
9277
+ z43.object({
9278
+ kind: z43.literal("provide_product_media"),
9279
+ href: z43.string().min(1)
9076
9280
  }),
9077
- z42.object({
9078
- kind: z42.literal("generate_creatives"),
9079
- href: z42.string().min(1)
9281
+ z43.object({
9282
+ kind: z43.literal("generate_creatives"),
9283
+ href: z43.string().min(1)
9080
9284
  }),
9081
- z42.object({
9082
- kind: z42.literal("wait_for_creatives"),
9083
- href: z42.string().min(1)
9285
+ z43.object({
9286
+ kind: z43.literal("wait_for_creatives"),
9287
+ href: z43.string().min(1)
9084
9288
  }),
9085
- z42.object({
9086
- kind: z42.literal("repair_creatives"),
9087
- href: z42.string().min(1)
9289
+ z43.object({
9290
+ kind: z43.literal("repair_creatives"),
9291
+ href: z43.string().min(1)
9088
9292
  }),
9089
- z42.object({
9090
- kind: z42.literal("complete_creative_set"),
9091
- href: z42.string().min(1)
9293
+ z43.object({
9294
+ kind: z43.literal("complete_creative_set"),
9295
+ href: z43.string().min(1)
9092
9296
  }),
9093
- z42.object({
9094
- kind: z42.literal("connect_shopify"),
9095
- href: z42.string().min(1)
9297
+ z43.object({
9298
+ kind: z43.literal("connect_shopify"),
9299
+ href: z43.string().min(1)
9096
9300
  }),
9097
- z42.object({
9098
- kind: z42.literal("publish_storefront"),
9099
- href: z42.string().min(1)
9301
+ z43.object({
9302
+ kind: z43.literal("publish_storefront"),
9303
+ href: z43.string().min(1)
9100
9304
  }),
9101
- z42.object({
9102
- kind: z42.literal("wait_for_storefront"),
9103
- href: z42.string().min(1)
9305
+ z43.object({
9306
+ kind: z43.literal("wait_for_storefront"),
9307
+ href: z43.string().min(1)
9104
9308
  }),
9105
- z42.object({
9106
- kind: z42.literal("connect_meta"),
9107
- href: z42.string().min(1)
9309
+ z43.object({
9310
+ kind: z43.literal("connect_meta"),
9311
+ href: z43.string().min(1)
9108
9312
  }),
9109
- z42.object({
9110
- kind: z42.literal("create_meta_campaign"),
9111
- href: z42.string().min(1)
9313
+ z43.object({
9314
+ kind: z43.literal("create_meta_campaign"),
9315
+ href: z43.string().min(1)
9112
9316
  }),
9113
- z42.object({
9114
- kind: z42.literal("repair_meta_campaign"),
9115
- href: z42.string().min(1)
9317
+ z43.object({
9318
+ kind: z43.literal("repair_meta_campaign"),
9319
+ href: z43.string().min(1)
9116
9320
  }),
9117
- z42.object({
9118
- kind: z42.literal("review_paused_campaign"),
9119
- href: z42.string().min(1)
9321
+ z43.object({
9322
+ kind: z43.literal("review_paused_campaign"),
9323
+ href: z43.string().min(1)
9120
9324
  })
9121
9325
  ]);
9122
- var DropshippingProjectResponseSchema = z42.object({
9123
- id: z42.string().uuid(),
9124
- taskId: z42.string().uuid().nullable(),
9326
+ var DropshippingProjectResponseSchema = z43.object({
9327
+ id: z43.string().uuid(),
9328
+ taskId: z43.string().uuid().nullable(),
9125
9329
  status: DropshippingProjectStatusSchema,
9126
- originKey: z42.string().min(1),
9127
- researchRunId: z42.string().uuid().nullable(),
9128
- candidateSourceId: z42.string().nullable(),
9129
- productName: z42.string().nullable(),
9330
+ originKey: z43.string().min(1),
9331
+ researchRunId: z43.string().uuid().nullable(),
9332
+ candidateSourceId: z43.string().nullable(),
9333
+ productName: z43.string().nullable(),
9130
9334
  productUrl: HttpUrlSchema.nullable(),
9131
- referenceImageUrl: z42.string().url().nullable(),
9132
- hasReferenceImage: z42.boolean(),
9133
- targetCountry: z42.string().nullable(),
9134
- languageCode: z42.string().nullable(),
9335
+ referenceImageUrl: z43.string().url().nullable(),
9336
+ hasReferenceImage: z43.boolean(),
9337
+ targetCountry: z43.string().nullable(),
9338
+ languageCode: z43.string().nullable(),
9135
9339
  costingInput: DropshippingProjectCostingInputSchema.nullable(),
9136
9340
  costingResult: DropshippingProjectCostingResultSchema.nullable(),
9137
- costingVersion: z42.number().int().positive().nullable(),
9138
- version: z42.number().int().positive(),
9341
+ costingVersion: z43.number().int().positive().nullable(),
9342
+ version: z43.number().int().positive(),
9139
9343
  phase: DropshippingProjectPhaseSchema,
9140
9344
  nextAction: DropshippingProjectNextActionSchema.nullable(),
9141
9345
  rewards: DropshippingProjectRewardProgressSchema,
9142
- createdAt: z42.string().datetime(),
9143
- updatedAt: z42.string().datetime()
9346
+ createdAt: z43.string().datetime(),
9347
+ updatedAt: z43.string().datetime()
9144
9348
  });
9145
9349
 
9146
9350
  // ../../packages/types/src/scene-subject-contract.ts
9147
- import { z as z43 } from "zod";
9148
- var SceneSubjectContractSchema = z43.object({
9149
- maxPeople: z43.number().int().min(1).max(10),
9150
- minPeople: z43.number().int().min(0).max(10),
9151
- minDistinctPeople: z43.number().int().min(0).max(10),
9152
- maxHands: z43.number().int().min(0).max(20)
9351
+ import { z as z44 } from "zod";
9352
+ var SceneSubjectContractSchema = z44.object({
9353
+ maxPeople: z44.number().int().min(1).max(10),
9354
+ minPeople: z44.number().int().min(0).max(10),
9355
+ minDistinctPeople: z44.number().int().min(0).max(10),
9356
+ maxHands: z44.number().int().min(0).max(20)
9153
9357
  }).superRefine((value, ctx) => {
9154
9358
  if (value.minPeople > value.maxPeople) {
9155
9359
  ctx.addIssue({
9156
- code: z43.ZodIssueCode.custom,
9360
+ code: z44.ZodIssueCode.custom,
9157
9361
  path: [
9158
9362
  "minPeople"
9159
9363
  ],
@@ -9162,7 +9366,7 @@ var SceneSubjectContractSchema = z43.object({
9162
9366
  }
9163
9367
  if (value.minDistinctPeople > value.maxPeople) {
9164
9368
  ctx.addIssue({
9165
- code: z43.ZodIssueCode.custom,
9369
+ code: z44.ZodIssueCode.custom,
9166
9370
  path: [
9167
9371
  "minDistinctPeople"
9168
9372
  ],
@@ -9171,7 +9375,7 @@ var SceneSubjectContractSchema = z43.object({
9171
9375
  }
9172
9376
  if (value.minDistinctPeople > value.minPeople) {
9173
9377
  ctx.addIssue({
9174
- code: z43.ZodIssueCode.custom,
9378
+ code: z44.ZodIssueCode.custom,
9175
9379
  path: [
9176
9380
  "minDistinctPeople"
9177
9381
  ],
@@ -9181,7 +9385,7 @@ var SceneSubjectContractSchema = z43.object({
9181
9385
  });
9182
9386
 
9183
9387
  // ../../packages/types/src/service-creative.ts
9184
- import { z as z44 } from "zod";
9388
+ import { z as z45 } from "zod";
9185
9389
  var SERVICE_CREATIVE_PUBLIC_PRESETS = [
9186
9390
  "auto",
9187
9391
  "ugc_dynamic",
@@ -9189,19 +9393,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
9189
9393
  "cinematic",
9190
9394
  "wild_card"
9191
9395
  ];
9192
- var ServiceCreativePresetSchema = z44.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
9396
+ var ServiceCreativePresetSchema = z45.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
9193
9397
  var SERVICE_REVIEW_MODES = [
9194
9398
  "automatic",
9195
9399
  "guided"
9196
9400
  ];
9197
- var ServiceReviewModeSchema = z44.enum(SERVICE_REVIEW_MODES);
9401
+ var ServiceReviewModeSchema = z45.enum(SERVICE_REVIEW_MODES);
9198
9402
  var SERVICE_HIGGSFIELD_MODES = [
9199
9403
  "product_review",
9200
9404
  "ugc_how_to",
9201
9405
  "tv_spot",
9202
9406
  "wild_card"
9203
9407
  ];
9204
- var ServiceHiggsfieldModeSchema = z44.enum(SERVICE_HIGGSFIELD_MODES);
9408
+ var ServiceHiggsfieldModeSchema = z45.enum(SERVICE_HIGGSFIELD_MODES);
9205
9409
  var SERVICE_MEDIA_ROLES = [
9206
9410
  "screen_demo",
9207
9411
  "ad_reference",
@@ -9209,20 +9413,20 @@ var SERVICE_MEDIA_ROLES = [
9209
9413
  "presenter_reference",
9210
9414
  "brand_asset"
9211
9415
  ];
9212
- var ServiceMediaRoleSchema = z44.enum(SERVICE_MEDIA_ROLES);
9213
- var ServiceMediaAssetSchema = z44.object({
9416
+ var ServiceMediaRoleSchema = z45.enum(SERVICE_MEDIA_ROLES);
9417
+ var ServiceMediaAssetSchema = z45.object({
9214
9418
  role: ServiceMediaRoleSchema,
9215
- s3Key: z44.string().min(1),
9419
+ s3Key: z45.string().min(1),
9216
9420
  /** HMAC ticket returned by the authenticated service-media presign route. */
9217
- uploadToken: z44.string().min(1).optional(),
9218
- mimeType: z44.string().min(1),
9219
- durationSec: z44.number().positive().max(30).optional(),
9220
- sourceName: z44.string().max(255).optional()
9421
+ uploadToken: z45.string().min(1).optional(),
9422
+ mimeType: z45.string().min(1),
9423
+ durationSec: z45.number().positive().max(30).optional(),
9424
+ sourceName: z45.string().max(255).optional()
9221
9425
  }).superRefine((asset, ctx) => {
9222
9426
  const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
9223
9427
  if (isVideoRole && !asset.mimeType.startsWith("video/")) {
9224
9428
  ctx.addIssue({
9225
- code: z44.ZodIssueCode.custom,
9429
+ code: z45.ZodIssueCode.custom,
9226
9430
  message: `${asset.role} requires video media`,
9227
9431
  path: [
9228
9432
  "mimeType"
@@ -9231,7 +9435,7 @@ var ServiceMediaAssetSchema = z44.object({
9231
9435
  }
9232
9436
  if (isVideoRole && asset.durationSec === void 0) {
9233
9437
  ctx.addIssue({
9234
- code: z44.ZodIssueCode.custom,
9438
+ code: z45.ZodIssueCode.custom,
9235
9439
  message: `${asset.role} requires durationSec`,
9236
9440
  path: [
9237
9441
  "durationSec"
@@ -9240,7 +9444,7 @@ var ServiceMediaAssetSchema = z44.object({
9240
9444
  }
9241
9445
  if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
9242
9446
  ctx.addIssue({
9243
- code: z44.ZodIssueCode.custom,
9447
+ code: z45.ZodIssueCode.custom,
9244
9448
  message: `${asset.role} requires image media`,
9245
9449
  path: [
9246
9450
  "mimeType"
@@ -9248,13 +9452,13 @@ var ServiceMediaAssetSchema = z44.object({
9248
9452
  });
9249
9453
  }
9250
9454
  });
9251
- var ServiceMediaBundleSchema = z44.object({
9252
- assets: z44.array(ServiceMediaAssetSchema).max(15).default([])
9455
+ var ServiceMediaBundleSchema = z45.object({
9456
+ assets: z45.array(ServiceMediaAssetSchema).max(15).default([])
9253
9457
  }).superRefine((bundle, ctx) => {
9254
9458
  const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
9255
9459
  if (adReferences.length > 5) {
9256
9460
  ctx.addIssue({
9257
- code: z44.ZodIssueCode.custom,
9461
+ code: z45.ZodIssueCode.custom,
9258
9462
  message: "A maximum of five ad_reference videos is allowed",
9259
9463
  path: [
9260
9464
  "assets"
@@ -9264,7 +9468,7 @@ var ServiceMediaBundleSchema = z44.object({
9264
9468
  const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
9265
9469
  if (screenDemos.length > 3) {
9266
9470
  ctx.addIssue({
9267
- code: z44.ZodIssueCode.custom,
9471
+ code: z45.ZodIssueCode.custom,
9268
9472
  message: "A maximum of three screen_demo videos is allowed",
9269
9473
  path: [
9270
9474
  "assets"
@@ -9281,33 +9485,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
9281
9485
  "product",
9282
9486
  "other"
9283
9487
  ];
9284
- var ServiceMediaAnalysisKindSchema = z44.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
9285
- var ServiceMediaAnalysisItemSchema = z44.object({
9286
- assetIndex: z44.number().int().nonnegative(),
9287
- mediaType: z44.enum([
9488
+ var ServiceMediaAnalysisKindSchema = z45.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
9489
+ var ServiceMediaAnalysisItemSchema = z45.object({
9490
+ assetIndex: z45.number().int().nonnegative(),
9491
+ mediaType: z45.enum([
9288
9492
  "image",
9289
9493
  "video"
9290
9494
  ]),
9291
9495
  kind: ServiceMediaAnalysisKindSchema,
9292
- description: z44.string().min(1).max(1200),
9293
- tags: z44.array(z44.string().min(1).max(80)).max(12),
9294
- suggestedRoles: z44.array(ServiceMediaRoleSchema).max(4),
9295
- visiblePeople: z44.number().int().nonnegative().max(20).optional(),
9296
- ocrText: z44.string().max(2e3).optional(),
9297
- speechSummary: z44.string().max(2e3).optional()
9298
- });
9299
- var ServiceMediaAnalysisSchema = z44.object({
9300
- version: z44.literal(1),
9301
- analyzedAt: z44.string().datetime(),
9302
- pricingAction: z44.literal("service-media-analysis"),
9303
- assetFingerprint: z44.string().regex(/^[a-f0-9]{64}$/),
9496
+ description: z45.string().min(1).max(1200),
9497
+ tags: z45.array(z45.string().min(1).max(80)).max(12),
9498
+ suggestedRoles: z45.array(ServiceMediaRoleSchema).max(4),
9499
+ visiblePeople: z45.number().int().nonnegative().max(20).optional(),
9500
+ ocrText: z45.string().max(2e3).optional(),
9501
+ speechSummary: z45.string().max(2e3).optional()
9502
+ });
9503
+ var ServiceMediaAnalysisSchema = z45.object({
9504
+ version: z45.literal(1),
9505
+ analyzedAt: z45.string().datetime(),
9506
+ pricingAction: z45.literal("service-media-analysis"),
9507
+ assetFingerprint: z45.string().regex(/^[a-f0-9]{64}$/),
9304
9508
  /** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
9305
- language: z44.enum([
9509
+ language: z45.enum([
9306
9510
  "es",
9307
9511
  "en",
9308
9512
  "pt"
9309
9513
  ]).optional(),
9310
- items: z44.array(ServiceMediaAnalysisItemSchema).max(15)
9514
+ items: z45.array(ServiceMediaAnalysisItemSchema).max(15)
9311
9515
  });
9312
9516
 
9313
9517
  // ../../packages/types/src/productmaker-capability-registry.ts
@@ -9365,6 +9569,7 @@ function slot(name, required, mcp2, whatsapp = mcp2, source = "user") {
9365
9569
  }
9366
9570
  __name(slot, "slot");
9367
9571
  var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
9572
+ "actionKind",
9368
9573
  "actorId",
9369
9574
  "adAccountId",
9370
9575
  "advertiserId",
@@ -9823,7 +10028,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9823
10028
  ], [
9824
10029
  "creative"
9825
10030
  ]),
9826
- defineCapability("generate_landing", "75947c5a70d26305ca07ab8d7bc0be710e862eb9319010ee66dd43e64fab2050", "credit_consuming", "creative", [
10031
+ defineCapability("generate_landing", "67520417176396516cd5ad358dca39f70a5c4a74169eecd85987217226c12251", "credit_consuming", "creative", [
9827
10032
  "credit_consuming",
9828
10033
  "state_change"
9829
10034
  ], [
@@ -9868,12 +10073,14 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9868
10073
  "competitorScanEnabled",
9869
10074
  "landingStrategy",
9870
10075
  "landingImageFormat",
10076
+ "pauseForAngleReview",
9871
10077
  // MCP-only, like create_product_task's aiDecidesAll.
9872
10078
  "aiDecidesAll"
9873
10079
  ], [
9874
10080
  "competitorScanEnabled",
9875
10081
  "landingStrategy",
9876
- "landingImageFormat"
10082
+ "landingImageFormat",
10083
+ "pauseForAngleReview"
9877
10084
  ])
9878
10085
  ], [
9879
10086
  "product_reference"
@@ -10189,7 +10396,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10189
10396
  ], [
10190
10397
  "meta_publication"
10191
10398
  ]),
10192
- defineCapability("publish_to_shopify", "ec37111aac872e6be22f34c8b547c1e4b58875f798c48e24ca9ca067a0a22d8b", "external_mutation", "publishing", [
10399
+ defineCapability("publish_to_shopify", "6a75ad9bfe04aed24ed41564011ac785657bab12caccd4d78c1de846ce798e82", "external_mutation", "publishing", [
10193
10400
  "external_mutation",
10194
10401
  "state_change"
10195
10402
  ], [
@@ -10209,7 +10416,13 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
10209
10416
  slot("publication_target", false, [
10210
10417
  "publicationMode",
10211
10418
  "targetProductId"
10212
- ])
10419
+ ]),
10420
+ // MCP + web only: the WhatsApp publish tool exposes no announcement fields,
10421
+ // and claiming them here breaks the schema-parity invariant.
10422
+ slot("announcement_bar", false, [
10423
+ "announcementBarEnabled",
10424
+ "announcementBarText"
10425
+ ], [])
10213
10426
  ], [
10214
10427
  "product",
10215
10428
  "shop"
@@ -10383,8 +10596,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_C
10383
10596
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
10384
10597
 
10385
10598
  // ../../packages/types/src/actor.ts
10386
- import { z as z45 } from "zod";
10387
- var ActorStatusSchema = z45.enum([
10599
+ import { z as z46 } from "zod";
10600
+ var ActorStatusSchema = z46.enum([
10388
10601
  "draft",
10389
10602
  "generating",
10390
10603
  "ready",
@@ -10393,18 +10606,18 @@ var ActorStatusSchema = z45.enum([
10393
10606
  var ACTOR_NAME_MAX_CHARS = 60;
10394
10607
  var ACTOR_DESCRIPTION_MAX_CHARS = 600;
10395
10608
  var ACTOR_CANDIDATE_COUNT = 4;
10396
- var ActorGenderSchema = z45.enum([
10609
+ var ActorGenderSchema = z46.enum([
10397
10610
  "female",
10398
10611
  "male",
10399
10612
  "non_binary"
10400
10613
  ]);
10401
- var ActorSchema = z45.object({
10402
- id: z45.string(),
10403
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS),
10404
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
10614
+ var ActorSchema = z46.object({
10615
+ id: z46.string(),
10616
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
10617
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
10405
10618
  gender: ActorGenderSchema,
10406
- voiceId: z45.string().nullable().optional(),
10407
- brandId: z45.string().nullable().optional(),
10619
+ voiceId: z46.string().nullable().optional(),
10620
+ brandId: z46.string().nullable().optional(),
10408
10621
  status: ActorStatusSchema,
10409
10622
  /**
10410
10623
  * A candidate-grid run is in flight RIGHT NOW.
@@ -10416,36 +10629,36 @@ var ActorSchema = z45.object({
10416
10629
  * timestamp with a TTL, so an orphaned run reads as `false` instead of
10417
10630
  * pinning the personaje forever.
10418
10631
  */
10419
- generating: z45.boolean().optional(),
10420
- photoUrl: z45.string().url().nullable().optional(),
10421
- avatarUrl: z45.string().url().nullable().optional(),
10422
- candidateUrls: z45.array(z45.string().url()).optional(),
10632
+ generating: z46.boolean().optional(),
10633
+ photoUrl: z46.string().url().nullable().optional(),
10634
+ avatarUrl: z46.string().url().nullable().optional(),
10635
+ candidateUrls: z46.array(z46.string().url()).optional(),
10423
10636
  /** Indices of candidateUrls that failed the anatomy check (Task 6). */
10424
- flaggedCandidateIndices: z45.array(z45.number().int()).optional()
10637
+ flaggedCandidateIndices: z46.array(z46.number().int()).optional()
10425
10638
  });
10426
- var CreateActorRequest = z45.object({
10427
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS),
10428
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
10639
+ var CreateActorRequest = z46.object({
10640
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
10641
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
10429
10642
  gender: ActorGenderSchema,
10430
- voiceId: z45.string().optional(),
10431
- brandId: z45.string().optional(),
10432
- sourcePhotoKey: z45.string().optional()
10643
+ voiceId: z46.string().optional(),
10644
+ brandId: z46.string().optional(),
10645
+ sourcePhotoKey: z46.string().optional()
10433
10646
  }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
10434
10647
  message: "Se necesita una descripci\xF3n o una foto"
10435
10648
  });
10436
- var UpdateActorRequest = z45.object({
10437
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
10438
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
10649
+ var UpdateActorRequest = z46.object({
10650
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
10651
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
10439
10652
  gender: ActorGenderSchema.optional(),
10440
- voiceId: z45.string().nullable().optional(),
10441
- brandId: z45.string().nullable().optional()
10653
+ voiceId: z46.string().nullable().optional(),
10654
+ brandId: z46.string().nullable().optional()
10442
10655
  });
10443
- var ApproveActorRequest = z45.object({
10444
- candidateIndex: z45.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
10656
+ var ApproveActorRequest = z46.object({
10657
+ candidateIndex: z46.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
10445
10658
  });
10446
10659
 
10447
10660
  // ../../packages/types/src/referral-dashboard.ts
10448
- import { z as z46 } from "zod";
10661
+ import { z as z47 } from "zod";
10449
10662
  var REFERRAL_DASHBOARD_RAW_STATUSES = [
10450
10663
  "ACTIVE",
10451
10664
  "PENDING",
@@ -10464,66 +10677,66 @@ var REFERRAL_DASHBOARD_PLAN_KINDS = [
10464
10677
  "last",
10465
10678
  "none"
10466
10679
  ];
10467
- var ReferralDashboardRawStatusSchema = z46.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
10468
- var ReferralDashboardStateSchema = z46.enum(REFERRAL_DASHBOARD_STATES);
10469
- var ReferralDashboardPlanKindSchema = z46.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
10470
- var ReferralDashboardStatusSchema = z46.enum([
10680
+ var ReferralDashboardRawStatusSchema = z47.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
10681
+ var ReferralDashboardStateSchema = z47.enum(REFERRAL_DASHBOARD_STATES);
10682
+ var ReferralDashboardPlanKindSchema = z47.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
10683
+ var ReferralDashboardStatusSchema = z47.enum([
10471
10684
  "all",
10472
10685
  "active",
10473
10686
  "pending",
10474
10687
  "inactive"
10475
10688
  ]).default("all");
10476
- var ReferralDashboardPlanSchema = z46.string().trim().min(1).max(100).optional();
10477
- var ReferralDashboardFiltersSchema = z46.object({
10689
+ var ReferralDashboardPlanSchema = z47.string().trim().min(1).max(100).optional();
10690
+ var ReferralDashboardFiltersSchema = z47.object({
10478
10691
  status: ReferralDashboardStatusSchema,
10479
10692
  plan: ReferralDashboardPlanSchema
10480
10693
  });
10481
- var ReferralDashboardSubscriptionSchema = z46.object({
10482
- planName: z46.string().nullable(),
10694
+ var ReferralDashboardSubscriptionSchema = z47.object({
10695
+ planName: z47.string().nullable(),
10483
10696
  planKind: ReferralDashboardPlanKindSchema,
10484
10697
  rawStatus: ReferralDashboardRawStatusSchema.nullable(),
10485
10698
  state: ReferralDashboardStateSchema,
10486
- currentPeriodEnd: z46.string().datetime({
10699
+ currentPeriodEnd: z47.string().datetime({
10487
10700
  offset: true
10488
10701
  }).nullable(),
10489
- cancelAtPeriodEnd: z46.boolean()
10702
+ cancelAtPeriodEnd: z47.boolean()
10490
10703
  });
10491
- var ReferralDashboardReferralSchema = z46.object({
10492
- id: z46.string().min(1),
10493
- registeredAt: z46.string().datetime({
10704
+ var ReferralDashboardReferralSchema = z47.object({
10705
+ id: z47.string().min(1),
10706
+ registeredAt: z47.string().datetime({
10494
10707
  offset: true
10495
10708
  }),
10496
10709
  // Kept in the response for compatibility with existing referral API clients.
10497
10710
  // The referrer dashboard intentionally does not render these legacy fields.
10498
- expiresAt: z46.string().datetime({
10711
+ expiresAt: z47.string().datetime({
10499
10712
  offset: true
10500
10713
  }).nullable().optional(),
10501
- status: z46.string().min(1).optional(),
10502
- referredUser: z46.object({
10503
- email: z46.string().nullable()
10714
+ status: z47.string().min(1).optional(),
10715
+ referredUser: z47.object({
10716
+ email: z47.string().nullable()
10504
10717
  }),
10505
10718
  subscription: ReferralDashboardSubscriptionSchema
10506
10719
  });
10507
- var ReferralDashboardReferralsPageSchema = z46.object({
10508
- referrals: z46.array(ReferralDashboardReferralSchema),
10509
- total: z46.number().int().nonnegative()
10720
+ var ReferralDashboardReferralsPageSchema = z47.object({
10721
+ referrals: z47.array(ReferralDashboardReferralSchema),
10722
+ total: z47.number().int().nonnegative()
10510
10723
  });
10511
- var ReferralDashboardPlanBreakdownSchema = z46.object({
10512
- planName: z46.string().nullable(),
10513
- count: z46.number().int().nonnegative()
10724
+ var ReferralDashboardPlanBreakdownSchema = z47.object({
10725
+ planName: z47.string().nullable(),
10726
+ count: z47.number().int().nonnegative()
10514
10727
  });
10515
- var ReferralDashboardMonthlyRegistrationSchema = z46.object({
10516
- month: z46.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
10517
- count: z46.number().int().nonnegative()
10728
+ var ReferralDashboardMonthlyRegistrationSchema = z47.object({
10729
+ month: z47.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
10730
+ count: z47.number().int().nonnegative()
10518
10731
  });
10519
- var ReferralDashboardOverviewSchema = z46.object({
10520
- totalReferredUsers: z46.number().int().nonnegative(),
10521
- activeUsers: z46.number().int().nonnegative(),
10522
- pendingUsers: z46.number().int().nonnegative(),
10523
- inactiveUsers: z46.number().int().nonnegative(),
10524
- noPlanUsers: z46.number().int().nonnegative(),
10525
- planBreakdown: z46.array(ReferralDashboardPlanBreakdownSchema),
10526
- monthlyRegistrations: z46.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
10732
+ var ReferralDashboardOverviewSchema = z47.object({
10733
+ totalReferredUsers: z47.number().int().nonnegative(),
10734
+ activeUsers: z47.number().int().nonnegative(),
10735
+ pendingUsers: z47.number().int().nonnegative(),
10736
+ inactiveUsers: z47.number().int().nonnegative(),
10737
+ noPlanUsers: z47.number().int().nonnegative(),
10738
+ planBreakdown: z47.array(ReferralDashboardPlanBreakdownSchema),
10739
+ monthlyRegistrations: z47.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
10527
10740
  });
10528
10741
 
10529
10742
  // src/resolve-media.ts
@@ -10766,10 +10979,10 @@ async function resolveMediaRef(ref, opts) {
10766
10979
  __name(resolveMediaRef, "resolveMediaRef");
10767
10980
 
10768
10981
  // src/tools/shared.ts
10769
- import { z as z47 } from "zod";
10770
- var Lang = z47.string().min(2).max(10);
10771
- var Country = z47.string().min(2).max(3);
10772
- var LandingStrategyEnum = z47.enum([
10982
+ import { z as z48 } from "zod";
10983
+ var Lang = z48.string().min(2).max(10);
10984
+ var Country = z48.string().min(2).max(3);
10985
+ var LandingStrategyEnum = z48.enum([
10773
10986
  "auto",
10774
10987
  "classic",
10775
10988
  "pain_solution",
@@ -10778,12 +10991,12 @@ var LandingStrategyEnum = z47.enum([
10778
10991
  "offer_value",
10779
10992
  "lifestyle_identity"
10780
10993
  ]);
10781
- var LandingImageFormatEnum = z47.enum([
10994
+ var LandingImageFormatEnum = z48.enum([
10782
10995
  "9:16",
10783
10996
  "4:5",
10784
10997
  "1:1"
10785
10998
  ]);
10786
- var VideoPresetEnum = z47.enum([
10999
+ var VideoPresetEnum = z48.enum([
10787
11000
  "auto",
10788
11001
  "ugc_dynamic",
10789
11002
  "ugc_how_to",
@@ -10796,7 +11009,7 @@ var VideoPresetEnum = z47.enum([
10796
11009
  "pixar_problem",
10797
11010
  "pixar_sassy"
10798
11011
  ]);
10799
- var TaskCreativeVariantVideoPresetEnum = z47.enum([
11012
+ var TaskCreativeVariantVideoPresetEnum = z48.enum([
10800
11013
  "auto",
10801
11014
  "ugc_dynamic",
10802
11015
  "ugc_how_to",
@@ -10867,7 +11080,7 @@ function resolveVideoPreset(preset) {
10867
11080
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
10868
11081
  }
10869
11082
  __name(resolveVideoPreset, "resolveVideoPreset");
10870
- var VideoTierEnum = z47.enum([
11083
+ var VideoTierEnum = z48.enum([
10871
11084
  "basic"
10872
11085
  ]);
10873
11086
  function resolveVideoTier(_tier) {
@@ -10882,17 +11095,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
10882
11095
  return durationSeconds === 30 ? "double_30s" : "single_15s";
10883
11096
  }
10884
11097
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
10885
- var VideoResolutionEnum = z47.enum([
11098
+ var VideoResolutionEnum = z48.enum([
10886
11099
  "480p",
10887
11100
  "720p"
10888
11101
  ]);
10889
- var VideoStyleEnum = z47.enum([
11102
+ var VideoStyleEnum = z48.enum([
10890
11103
  "ugc",
10891
11104
  "cartoon_3d",
10892
11105
  "product_only",
10893
11106
  "ai_dynamic"
10894
11107
  ]);
10895
- var NarrativeStyleEnum = z47.enum([
11108
+ var NarrativeStyleEnum = z48.enum([
10896
11109
  "review",
10897
11110
  "ugc_how_to",
10898
11111
  "problem_product_cta",
@@ -10909,7 +11122,7 @@ var NarrativeStyleEnum = z47.enum([
10909
11122
  "ugc_skeptic_confession",
10910
11123
  "ugc_demo_review"
10911
11124
  ]);
10912
- var PublicNarrativeStyleEnum = z47.enum([
11125
+ var PublicNarrativeStyleEnum = z48.enum([
10913
11126
  "review",
10914
11127
  "ugc_how_to",
10915
11128
  "ugc_unboxing_chaos",
@@ -10921,7 +11134,7 @@ var PublicNarrativeStyleEnum = z47.enum([
10921
11134
  "animated_problem_pixar",
10922
11135
  "sassy_product_pixar"
10923
11136
  ]);
10924
- var VideoModelEnum = z47.enum([
11137
+ var VideoModelEnum = z48.enum([
10925
11138
  "veo",
10926
11139
  "wan",
10927
11140
  "bytedance",
@@ -10929,29 +11142,29 @@ var VideoModelEnum = z47.enum([
10929
11142
  "grok",
10930
11143
  "kling"
10931
11144
  ]);
10932
- var ProductReferenceUserChoiceEnum = z47.enum([
11145
+ var ProductReferenceUserChoiceEnum = z48.enum([
10933
11146
  "same_product",
10934
11147
  "variants",
10935
11148
  "different_products",
10936
11149
  "combo_distinct"
10937
11150
  ]);
10938
- var ProductReferenceCandidateGroupingEnum = z47.enum([
11151
+ var ProductReferenceCandidateGroupingEnum = z48.enum([
10939
11152
  "same_product_same_variant",
10940
11153
  "same_product_different_variants",
10941
11154
  "different_products",
10942
11155
  "unclear"
10943
11156
  ]);
10944
- var ProductReferenceScopeEnum = z47.enum([
11157
+ var ProductReferenceScopeEnum = z48.enum([
10945
11158
  "base",
10946
11159
  "video_view_pack"
10947
11160
  ]);
10948
- var ProductReferenceResolutionDecisionEnum = z47.enum([
11161
+ var ProductReferenceResolutionDecisionEnum = z48.enum([
10949
11162
  "approve",
10950
11163
  "reject",
10951
11164
  "observed_only",
10952
11165
  "correct_view"
10953
11166
  ]);
10954
- var ProductReferenceRejectionReasonEnum = z47.enum([
11167
+ var ProductReferenceRejectionReasonEnum = z48.enum([
10955
11168
  "replacement_requested",
10956
11169
  "wrong_product_identity",
10957
11170
  "wrong_variant",
@@ -10962,13 +11175,13 @@ var ProductReferenceRejectionReasonEnum = z47.enum([
10962
11175
  "not_a_product_photo",
10963
11176
  "other"
10964
11177
  ]);
10965
- var GapDurationChoiceMirror = z47.object({
10966
- angleIndex: z47.number().int().nonnegative(),
10967
- recommendedSec: z47.union([
10968
- z47.literal(15),
10969
- z47.literal(30)
11178
+ var GapDurationChoiceMirror = z48.object({
11179
+ angleIndex: z48.number().int().nonnegative(),
11180
+ recommendedSec: z48.union([
11181
+ z48.literal(15),
11182
+ z48.literal(30)
10970
11183
  ]),
10971
- estimatedDeltaCredits: z47.number().int().nonnegative().optional()
11184
+ estimatedDeltaCredits: z48.number().int().nonnegative().optional()
10972
11185
  });
10973
11186
  function buildSelectedImageUrlsHash(urls) {
10974
11187
  const input = [
@@ -10978,13 +11191,13 @@ function buildSelectedImageUrlsHash(urls) {
10978
11191
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
10979
11192
  }
10980
11193
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
10981
- var ImageCreativeStyleEnum = z47.enum([
11194
+ var ImageCreativeStyleEnum = z48.enum([
10982
11195
  "studio",
10983
11196
  "floating",
10984
11197
  "ingredients",
10985
11198
  "in_use"
10986
11199
  ]);
10987
- var ImageCreativeArchetypeEnum = z47.enum([
11200
+ var ImageCreativeArchetypeEnum = z48.enum([
10988
11201
  "direct_response_product_ad",
10989
11202
  "hero_outcome",
10990
11203
  "offer_value_prop",
@@ -10993,20 +11206,20 @@ var ImageCreativeArchetypeEnum = z47.enum([
10993
11206
  "problem_solution",
10994
11207
  "lifestyle_identity"
10995
11208
  ]);
10996
- var ImageCreativeVisualTreatmentEnum = z47.enum([
11209
+ var ImageCreativeVisualTreatmentEnum = z48.enum([
10997
11210
  "performance_product_ad",
10998
11211
  "premium_dtc_carousel"
10999
11212
  ]);
11000
- var ImageCreativeAspectRatioEnum = z47.enum([
11213
+ var ImageCreativeAspectRatioEnum = z48.enum([
11001
11214
  "1:1",
11002
11215
  "4:5",
11003
11216
  "9:16",
11004
11217
  "16:9"
11005
11218
  ]);
11006
- var ImageRefSchema = z47.object({
11007
- url: z47.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
11008
- path: z47.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.'),
11009
- assetId: z47.string().uuid().optional().describe("ID opaco devuelto por check_media_upload. \xDAsalo para una imagen subida desde el MCP App; ProductMaker adopta el archivo directamente y el MCP nunca descarga sus bytes.")
11219
+ var ImageRefSchema = z48.object({
11220
+ url: z48.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
11221
+ path: z48.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.'),
11222
+ assetId: z48.string().uuid().optional().describe("ID opaco devuelto por check_media_upload. \xDAsalo para una imagen subida desde el MCP App; ProductMaker adopta el archivo directamente y el MCP nunca descarga sus bytes.")
11010
11223
  }).refine((v) => [
11011
11224
  v.url,
11012
11225
  v.path,
@@ -11014,11 +11227,11 @@ var ImageRefSchema = z47.object({
11014
11227
  ].filter(Boolean).length === 1, {
11015
11228
  message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
11016
11229
  });
11017
- var ImagesInput = z47.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
11230
+ var ImagesInput = z48.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
11018
11231
  const assetCount = images.filter((image) => Boolean(image.assetId)).length;
11019
11232
  if (assetCount > 0 && assetCount !== images.length) {
11020
11233
  ctx.addIssue({
11021
- code: z47.ZodIssueCode.custom,
11234
+ code: z48.ZodIssueCode.custom,
11022
11235
  message: "all image entries must use assetId, or all must use URL/path sources"
11023
11236
  });
11024
11237
  }
@@ -11088,14 +11301,14 @@ async function submitCreative(api, opts) {
11088
11301
  });
11089
11302
  }
11090
11303
  __name(submitCreative, "submitCreative");
11091
- var ResearchTargetCountryEnum = z47.enum([
11304
+ var ResearchTargetCountryEnum = z48.enum([
11092
11305
  "CO",
11093
11306
  "MX",
11094
11307
  "PE",
11095
11308
  "EC",
11096
11309
  "CL"
11097
11310
  ]);
11098
- var ResearchCategoryEnum = z47.enum([
11311
+ var ResearchCategoryEnum = z48.enum([
11099
11312
  "auto",
11100
11313
  "automotive",
11101
11314
  "kitchen",
@@ -11110,6 +11323,42 @@ var ResearchCategoryEnum = z47.enum([
11110
11323
  "garden",
11111
11324
  "cleaning"
11112
11325
  ]);
11326
+ var READS_INTERNAL = {
11327
+ readOnlyHint: true,
11328
+ destructiveHint: false,
11329
+ idempotentHint: true,
11330
+ openWorldHint: false
11331
+ };
11332
+ var READS_EXTERNAL = {
11333
+ readOnlyHint: true,
11334
+ destructiveHint: false,
11335
+ idempotentHint: true,
11336
+ openWorldHint: true
11337
+ };
11338
+ var CREATES_INTERNAL = {
11339
+ readOnlyHint: false,
11340
+ destructiveHint: false,
11341
+ idempotentHint: false,
11342
+ openWorldHint: false
11343
+ };
11344
+ var CREATES_EXTERNAL = {
11345
+ readOnlyHint: false,
11346
+ destructiveHint: false,
11347
+ idempotentHint: false,
11348
+ openWorldHint: true
11349
+ };
11350
+ var OVERWRITES_INTERNAL = {
11351
+ readOnlyHint: false,
11352
+ destructiveHint: true,
11353
+ idempotentHint: false,
11354
+ openWorldHint: false
11355
+ };
11356
+ var OVERWRITES_EXTERNAL = {
11357
+ readOnlyHint: false,
11358
+ destructiveHint: true,
11359
+ idempotentHint: false,
11360
+ openWorldHint: true
11361
+ };
11113
11362
 
11114
11363
  // src/errors.ts
11115
11364
  var McpErrorCode = {
@@ -11567,6 +11816,18 @@ function translateError(err) {
11567
11816
  }
11568
11817
  __name(translateError, "translateError");
11569
11818
 
11819
+ // src/tools/approve-refusal-message.ts
11820
+ function approveRefusalMessage(reason) {
11821
+ if (reason === "photo_review_pending") {
11822
+ return "No se inici\xF3 la generaci\xF3n: faltan por revisar las fotos del producto. Abre la tarea, aprueba las fotos y vuelve a intentar. No se cobr\xF3 nada.";
11823
+ }
11824
+ if (reason === "photos_not_ready") {
11825
+ return "No se inici\xF3 la generaci\xF3n: todav\xEDa estamos preparando las fotos del producto. Intenta de nuevo en un momento. No se cobr\xF3 nada.";
11826
+ }
11827
+ return "No se inici\xF3 la generaci\xF3n y no se cobr\xF3 nada. Los cambios siguen guardados: vuelve a intentar.";
11828
+ }
11829
+ __name(approveRefusalMessage, "approveRefusalMessage");
11830
+
11570
11831
  // src/asset-proxy.ts
11571
11832
  import { createHmac, timingSafeEqual } from "crypto";
11572
11833
  var TOKEN_TTL_SECONDS = 3600;
@@ -11748,54 +12009,54 @@ function renderOutputsMarkdown(outputs) {
11748
12009
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
11749
12010
 
11750
12011
  // src/tools/tasks.ts
11751
- var CreateProductTaskInput = z48.object({
12012
+ var CreateProductTaskInput = z49.object({
11752
12013
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
11753
- projectId: z48.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
11754
- productUrl: z48.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
12014
+ projectId: z49.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
12015
+ productUrl: z49.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
11755
12016
  language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
11756
12017
  country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
11757
- price: z48.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
11758
- compareAtPrice: z48.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
11759
- offer: z48.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
11760
- paymentMethods: z48.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
11761
- productContext: z48.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
11762
- productLabel: z48.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
12018
+ price: z49.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
12019
+ compareAtPrice: z49.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
12020
+ offer: z49.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
12021
+ paymentMethods: z49.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
12022
+ productContext: z49.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
12023
+ productLabel: z49.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
11763
12024
  videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
11764
- durationSeconds: z48.union([
11765
- z48.literal(15),
11766
- z48.literal(30)
12025
+ durationSeconds: z49.union([
12026
+ z49.literal(15),
12027
+ z49.literal(30)
11767
12028
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
11768
- pauseForAngleReview: z48.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
11769
- aiDecidesAll: z48.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n, as\xED que `pendingUserAction` no deber\xEDa aparecer. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
12029
+ pauseForAngleReview: z49.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
12030
+ aiDecidesAll: z49.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n, as\xED que `pendingUserAction` no deber\xEDa aparecer. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
11770
12031
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
11771
12032
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
11772
- multiAngleVideos: z48.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
12033
+ multiAngleVideos: z49.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
11773
12034
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "problem_product_cta" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
11774
- outputs: z48.array(z48.enum([
12035
+ outputs: z49.array(z49.enum([
11775
12036
  "landing",
11776
12037
  "video",
11777
12038
  "image"
11778
12039
  ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
11779
- competitorScanEnabled: z48.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
11780
- brandId: z48.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
11781
- actorId: z48.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
11782
- creativeDirection: z48.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
11783
- headline: z48.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
11784
- subheadline: z48.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
12040
+ competitorScanEnabled: z49.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
12041
+ brandId: z49.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
12042
+ actorId: z49.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
12043
+ creativeDirection: z49.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
12044
+ headline: z49.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar. Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. Se aplica a las IM\xC1GENES y a la portada de la landing de esta task (el video no lo usa; para el video usa scriptNotes). \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
12045
+ subheadline: z49.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres."),
11785
12046
  landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
11786
12047
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
11787
12048
  }).strict();
11788
- var CreateServiceVideoInput = z48.object({
11789
- landingUrl: z48.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
11790
- additionalContext: z48.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
12049
+ var CreateServiceVideoInput = z49.object({
12050
+ landingUrl: z49.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
12051
+ additionalContext: z49.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
11791
12052
  language: Lang.describe("Idioma del video: es, en o pt."),
11792
- voiceId: z48.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
11793
- brandId: z48.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
11794
- reviewMode: z48.enum([
12053
+ voiceId: z49.string().trim().min(1).optional().describe('Opcional. Voz de ElevenLabs para toda la narraci\xF3n. Usa "auto" para que la IA la alinee con el presentador o env\xEDa un ID concreto de la lista de voces de la web.'),
12054
+ brandId: z49.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
12055
+ reviewMode: z49.enum([
11795
12056
  "automatic",
11796
12057
  "guided"
11797
12058
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
11798
- videoPreset: z48.enum([
12059
+ videoPreset: z49.enum([
11799
12060
  "auto",
11800
12061
  "ugc_dynamic",
11801
12062
  "ugc_how_to",
@@ -11803,51 +12064,51 @@ var CreateServiceVideoInput = z48.object({
11803
12064
  "wild_card"
11804
12065
  ]).default("auto")
11805
12066
  }).strict();
11806
- var GetTaskStatusInput = z48.object({
11807
- taskId: z48.string().min(1),
11808
- waitSeconds: z48.number().int().min(0).max(120).optional()
11809
- });
11810
- var ListTasksInput = z48.object({
11811
- limit: z48.number().int().min(1).max(50).optional(),
11812
- offset: z48.number().int().min(0).optional(),
11813
- search: z48.string().optional()
11814
- });
11815
- var EditTaskDraftInput = z48.object({
11816
- taskId: z48.string().min(1),
11817
- angleIndex: z48.number().int().min(0),
11818
- angle: z48.object({
11819
- angle: z48.string().min(1).max(480),
11820
- buyerPersona: z48.string().min(1).max(360),
11821
- actor: z48.string().min(1).max(360).optional(),
11822
- actorId: z48.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
11823
- gender: z48.enum([
12067
+ var GetTaskStatusInput = z49.object({
12068
+ taskId: z49.string().min(1),
12069
+ waitSeconds: z49.number().int().min(0).max(120).optional()
12070
+ });
12071
+ var ListTasksInput = z49.object({
12072
+ limit: z49.number().int().min(1).max(50).optional(),
12073
+ offset: z49.number().int().min(0).optional(),
12074
+ search: z49.string().optional()
12075
+ });
12076
+ var EditTaskDraftInput = z49.object({
12077
+ taskId: z49.string().min(1),
12078
+ angleIndex: z49.number().int().min(0),
12079
+ angle: z49.object({
12080
+ angle: z49.string().min(1).max(480),
12081
+ buyerPersona: z49.string().min(1).max(360),
12082
+ actor: z49.string().min(1).max(360).optional(),
12083
+ actorId: z49.string().trim().min(1).optional().describe("Opcional. ID de un personaje guardado (usa list_actors para verlos). Si lo env\xEDas, su foto y su voz mandan sobre la descripci\xF3n de texto."),
12084
+ gender: z49.enum([
11824
12085
  "female",
11825
12086
  "male"
11826
12087
  ]).optional(),
11827
- scriptNotes: z48.string().max(2e3).optional()
12088
+ scriptNotes: z49.string().max(2e3).optional()
11828
12089
  })
11829
12090
  });
11830
- var PendingActionPayload = z48.discriminatedUnion("kind", [
11831
- z48.object({
11832
- kind: z48.literal("product_reference_set_approval"),
12091
+ var PendingActionPayload = z49.discriminatedUnion("kind", [
12092
+ z49.object({
12093
+ kind: z49.literal("product_reference_set_approval"),
11833
12094
  scope: ProductReferenceScopeEnum,
11834
- scopeKey: z48.string().min(1).max(120),
11835
- scopeSnapshotId: z48.string().uuid(),
11836
- expectedScopeHash: z48.string().min(1).max(128),
12095
+ scopeKey: z49.string().min(1).max(120),
12096
+ scopeSnapshotId: z49.string().uuid(),
12097
+ expectedScopeHash: z49.string().min(1).max(128),
11837
12098
  decision: ProductReferenceResolutionDecisionEnum,
11838
12099
  reason: ProductReferenceRejectionReasonEnum.optional(),
11839
- affectedRefKeys: z48.array(z48.string().min(1).max(200)).min(1).optional(),
11840
- recoveryAction: z48.enum([
12100
+ affectedRefKeys: z49.array(z49.string().min(1).max(200)).min(1).optional(),
12101
+ recoveryAction: z49.enum([
11841
12102
  "remove",
11842
12103
  "replace",
11843
12104
  "upload",
11844
12105
  "return_to_selection"
11845
12106
  ]).optional()
11846
12107
  }),
11847
- z48.object({
11848
- kind: z48.literal("product_reference_confirmation"),
12108
+ z49.object({
12109
+ kind: z49.literal("product_reference_confirmation"),
11849
12110
  userChoice: ProductReferenceUserChoiceEnum.optional(),
11850
- confirmedProductNames: z48.array(z48.string().min(1).max(120)).min(2).max(8).optional(),
12111
+ confirmedProductNames: z49.array(z49.string().min(1).max(120)).min(2).max(8).optional(),
11851
12112
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
11852
12113
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
11853
12114
  // product in several colors): renders one shot per color and stitches them,
@@ -11858,50 +12119,50 @@ var PendingActionPayload = z48.discriminatedUnion("kind", [
11858
12119
  // safe because multi-scene IS the intended render for a color variant set,
11859
12120
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
11860
12121
  // confirm call; the backend re-gates it (variants + variant_set only).
11861
- useMultiSceneShowcase: z48.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
12122
+ useMultiSceneShowcase: z49.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
11862
12123
  }),
11863
- z48.object({
11864
- kind: z48.literal("angle_review_approval"),
12124
+ z49.object({
12125
+ kind: z49.literal("angle_review_approval"),
11865
12126
  // All edits are optional — empty action body just approves the
11866
12127
  // backend-generated draft as-is. Field shapes mirror the angle-review
11867
12128
  // request contracts in @packages/types/wizard.ts; the handler routes to
11868
12129
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
11869
- customAngle: z48.string().min(1).max(480).optional(),
12130
+ customAngle: z49.string().min(1).max(480).optional(),
11870
12131
  // Keep the shared MCP action within the strictest downstream contract:
11871
12132
  // autopilot/image resume endpoints cap these fields at 1500 characters.
11872
- painPoints: z48.string().max(1500).optional(),
11873
- benefits: z48.string().max(1500).optional(),
11874
- features: z48.array(z48.string().min(1).max(300)).max(10).optional(),
11875
- gapDurationSeconds: z48.union([
11876
- z48.literal(15),
11877
- z48.literal(30)
12133
+ painPoints: z49.string().max(1500).optional(),
12134
+ benefits: z49.string().max(1500).optional(),
12135
+ features: z49.array(z49.string().min(1).max(300)).max(10).optional(),
12136
+ gapDurationSeconds: z49.union([
12137
+ z49.literal(15),
12138
+ z49.literal(30)
11878
12139
  ]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
11879
12140
  }),
11880
- z48.object({
11881
- kind: z48.literal("service_angle_approval"),
11882
- angleId: z48.string().uuid(),
11883
- content: z48.string().min(1).max(480).optional(),
11884
- creativeDirection: z48.string().min(1).max(1500).optional(),
11885
- actor: z48.string().min(1).max(360).optional()
12141
+ z49.object({
12142
+ kind: z49.literal("service_angle_approval"),
12143
+ angleId: z49.string().uuid(),
12144
+ content: z49.string().min(1).max(480).optional(),
12145
+ creativeDirection: z49.string().min(1).max(1500).optional(),
12146
+ actor: z49.string().min(1).max(360).optional()
11886
12147
  }),
11887
- z48.object({
11888
- kind: z48.literal("service_script_approval"),
11889
- voiceovers: z48.record(z48.string(), z48.string().min(1).max(600)).optional(),
11890
- visualDirections: z48.record(z48.string(), z48.string().min(1).max(1200)).optional()
12148
+ z49.object({
12149
+ kind: z49.literal("service_script_approval"),
12150
+ voiceovers: z49.record(z49.string(), z49.string().min(1).max(600)).optional(),
12151
+ visualDirections: z49.record(z49.string(), z49.string().min(1).max(1200)).optional()
11891
12152
  })
11892
12153
  ]);
11893
- var ResolvePendingActionInput = z48.object({
11894
- taskId: z48.string().min(1),
12154
+ var ResolvePendingActionInput = z49.object({
12155
+ taskId: z49.string().min(1),
11895
12156
  action: PendingActionPayload
11896
12157
  }).strict();
11897
12158
  function resolveVariantConfirmVideoTier(action) {
11898
12159
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
11899
12160
  }
11900
12161
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
11901
- var StatusForResolveSchema = z48.object({
12162
+ var StatusForResolveSchema = z49.object({
11902
12163
  // Metadata used to select the backend resume contract. Older API responses
11903
12164
  // may omit these fields, so the resolver keeps the generic fallback.
11904
- kind: z48.enum([
12165
+ kind: z49.enum([
11905
12166
  "autopilot",
11906
12167
  "video",
11907
12168
  "image",
@@ -11909,24 +12170,24 @@ var StatusForResolveSchema = z48.object({
11909
12170
  "mix",
11910
12171
  "service"
11911
12172
  ]).optional(),
11912
- selectedOutputs: z48.array(z48.enum([
12173
+ selectedOutputs: z49.array(z49.enum([
11913
12174
  "landing",
11914
12175
  "video",
11915
12176
  "image"
11916
12177
  ])).optional(),
11917
- videoAngleCount: z48.number().int().min(1).max(3).optional(),
11918
- ugcFormat: z48.enum([
12178
+ videoAngleCount: z49.number().int().min(1).max(3).optional(),
12179
+ ugcFormat: z49.enum([
11919
12180
  "single_8s",
11920
12181
  "single_15s",
11921
12182
  "double_30s"
11922
12183
  ]).optional(),
11923
- videoStyle: z48.enum([
12184
+ videoStyle: z49.enum([
11924
12185
  "ugc",
11925
12186
  "cartoon_3d",
11926
12187
  "product_only",
11927
12188
  "ai_dynamic"
11928
12189
  ]).optional(),
11929
- videoModel: z48.enum([
12190
+ videoModel: z49.enum([
11930
12191
  "veo",
11931
12192
  "wan",
11932
12193
  "bytedance",
@@ -11934,62 +12195,62 @@ var StatusForResolveSchema = z48.object({
11934
12195
  "grok",
11935
12196
  "kling"
11936
12197
  ]).optional(),
11937
- videoTier: z48.enum([
12198
+ videoTier: z49.enum([
11938
12199
  "legacy",
11939
12200
  "standard",
11940
12201
  "pro",
11941
12202
  "single_call"
11942
12203
  ]).optional(),
11943
- videoResolution: z48.enum([
12204
+ videoResolution: z49.enum([
11944
12205
  "480p",
11945
12206
  "720p"
11946
12207
  ]).optional(),
11947
12208
  narrativeStyle: NarrativeStyleEnum.optional(),
11948
- pendingUserAction: z48.object({
11949
- kind: z48.string(),
11950
- candidateGrouping: z48.string().optional(),
11951
- selectedImageUrls: z48.array(z48.string()).optional(),
11952
- proposedProductNames: z48.array(z48.string()).optional(),
12209
+ pendingUserAction: z49.object({
12210
+ kind: z49.string(),
12211
+ candidateGrouping: z49.string().optional(),
12212
+ selectedImageUrls: z49.array(z49.string()).optional(),
12213
+ proposedProductNames: z49.array(z49.string()).optional(),
11953
12214
  // Adaptive reference questions are server-owned. Echo only the evidence
11954
12215
  // identity and the choices shown by the current snapshot; never accept a
11955
12216
  // client-supplied reference set as authorization.
11956
- options: z48.array(ProductReferenceUserChoiceEnum).max(4).optional(),
11957
- mode: z48.enum([
12217
+ options: z49.array(ProductReferenceUserChoiceEnum).max(4).optional(),
12218
+ mode: z49.enum([
11958
12219
  "question",
11959
12220
  "visual_recovery"
11960
12221
  ]).optional(),
11961
- decisionKey: z48.string().optional(),
11962
- questionOrdinal: z48.number().int().min(1).max(5).optional(),
11963
- questionCeiling: z48.union([
11964
- z48.literal(3),
11965
- z48.literal(5)
12222
+ decisionKey: z49.string().optional(),
12223
+ questionOrdinal: z49.number().int().min(1).max(5).optional(),
12224
+ questionCeiling: z49.union([
12225
+ z49.literal(3),
12226
+ z49.literal(5)
11966
12227
  ]).optional(),
11967
- referenceEvidenceVersion: z48.number().int().positive().optional(),
11968
- expectedEvidenceHash: z48.string().optional(),
11969
- question: z48.object({
11970
- prompt: z48.string().min(1).max(240),
11971
- helpText: z48.string().min(1).max(300).optional(),
11972
- options: z48.array(z48.object({
12228
+ referenceEvidenceVersion: z49.number().int().positive().optional(),
12229
+ expectedEvidenceHash: z49.string().optional(),
12230
+ question: z49.object({
12231
+ prompt: z49.string().min(1).max(240),
12232
+ helpText: z49.string().min(1).max(300).optional(),
12233
+ options: z49.array(z49.object({
11973
12234
  value: ProductReferenceUserChoiceEnum,
11974
- label: z48.string().min(1).max(120),
11975
- description: z48.string().min(1).max(240).optional()
12235
+ label: z49.string().min(1).max(120),
12236
+ description: z49.string().min(1).max(240).optional()
11976
12237
  })).min(2).max(4)
11977
12238
  }).optional(),
11978
12239
  scope: ProductReferenceScopeEnum.optional(),
11979
- scopeKey: z48.string().optional(),
11980
- scopeSnapshotId: z48.string().uuid().optional(),
11981
- scopeHash: z48.string().optional(),
11982
- baseReferenceSetHash: z48.string().optional(),
11983
- phase: z48.enum([
12240
+ scopeKey: z49.string().optional(),
12241
+ scopeSnapshotId: z49.string().uuid().optional(),
12242
+ scopeHash: z49.string().optional(),
12243
+ baseReferenceSetHash: z49.string().optional(),
12244
+ phase: z49.enum([
11984
12245
  "preparation",
11985
12246
  "final"
11986
12247
  ]).optional(),
11987
- selectedRefKeys: z48.array(z48.string().min(1).max(200)).optional(),
11988
- allowedDecisions: z48.array(ProductReferenceResolutionDecisionEnum).optional(),
11989
- customAngleDraft: z48.string().optional(),
11990
- painPointsDraft: z48.string().optional(),
11991
- benefitsDraft: z48.string().optional(),
11992
- featuresDraft: z48.array(z48.string()).optional(),
12248
+ selectedRefKeys: z49.array(z49.string().min(1).max(200)).optional(),
12249
+ allowedDecisions: z49.array(ProductReferenceResolutionDecisionEnum).optional(),
12250
+ customAngleDraft: z49.string().optional(),
12251
+ painPointsDraft: z49.string().optional(),
12252
+ benefitsDraft: z49.string().optional(),
12253
+ featuresDraft: z49.array(z49.string()).optional(),
11993
12254
  // Recommendation consumed only when the caller explicitly sends
11994
12255
  // gapDurationSeconds: 30 in the angle approval action.
11995
12256
  gapDurationChoice: GapDurationChoiceMirror.optional(),
@@ -11998,17 +12259,17 @@ var StatusForResolveSchema = z48.object({
11998
12259
  // keys. `actorId` (the saved custom actor the picker stored) was silently
11999
12260
  // dropped here, so the approval handler below could not echo it back on the
12000
12261
  // PATCH — the selection looked accepted and reached no render.
12001
- angles: z48.array(z48.object({
12002
- id: z48.string().uuid(),
12003
- content: z48.string(),
12004
- creativeDirection: z48.string(),
12005
- actor: z48.string(),
12006
- actorId: z48.string().nullable().optional()
12262
+ angles: z49.array(z49.object({
12263
+ id: z49.string().uuid(),
12264
+ content: z49.string(),
12265
+ creativeDirection: z49.string(),
12266
+ actor: z49.string(),
12267
+ actorId: z49.string().nullable().optional()
12007
12268
  }).passthrough()).optional(),
12008
- panels: z48.array(z48.object({
12009
- beatId: z48.string(),
12010
- voiceover: z48.string(),
12011
- visualDirection: z48.string()
12269
+ panels: z49.array(z49.object({
12270
+ beatId: z49.string(),
12271
+ voiceover: z49.string(),
12272
+ visualDirection: z49.string()
12012
12273
  }).passthrough()).optional()
12013
12274
  }).optional()
12014
12275
  }).passthrough();
@@ -12047,7 +12308,7 @@ function resolveStatusTaskKind(status) {
12047
12308
  }
12048
12309
  __name(resolveStatusTaskKind, "resolveStatusTaskKind");
12049
12310
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
12050
- server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country. Para fotos en una laptop con MCP remoto llama `request_media_upload` con mediaKind=image y luego usa los assetId devueltos dentro de images; no pidas path ni URL cuando el App est\xE9 disponible.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, async (input) => {
12311
+ server.tool("create_product_task", "Crea una tarea de producto desde fotos o desde un proyecto guiado propio. Por defecto genera los 3 outputs (landing + video + image creatives) EN UN MISMO TASK. Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero) O projectId si el proyecto ya tiene una imagen guardada; adem\xE1s language y country. Para fotos en una laptop con MCP remoto llama `request_media_upload` con mediaKind=image y luego usa los assetId devueltos dentro de images; no pidas path ni URL cuando el App est\xE9 disponible.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son inputs de alto impacto en la landing y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si est\xE1 en LATAM.\n\nSi env\xEDas projectId, debe ser el proyecto propio que el usuario acaba de costear; el backend resolver\xE1 la imagen privada y conservar\xE1 todos los controles de cr\xE9ditos/confirmaci\xF3n. Si el usuario adjunta images junto con projectId, esas fotos reemplazan la referencia solo para este task.\n\nANTES de invocar, DILE CLARAMENTE qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task.\n\nUsa SOLO valores declarados en los enums.", CreateProductTaskInput.shape, CREATES_EXTERNAL, async (input) => {
12051
12312
  try {
12052
12313
  if (!input.images && !input.projectId) {
12053
12314
  throw new Error("images es requerido salvo que env\xEDes projectId.");
@@ -12115,7 +12376,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
12115
12376
  throw translateError(e);
12116
12377
  }
12117
12378
  });
12118
- server.tool("create_service_video", "Crea un video para un servicio desde su landing. Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, async (input) => {
12379
+ server.tool("create_service_video", "Crea un video para un servicio desde su landing. Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.", CreateServiceVideoInput.shape, CREATES_EXTERNAL, async (input) => {
12119
12380
  try {
12120
12381
  const created = await api.postJson("/v2/ingest", {
12121
12382
  taskType: "service",
@@ -12166,7 +12427,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
12166
12427
  throw translateError(e);
12167
12428
  }
12168
12429
  });
12169
- server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase, aparezca `pendingUserAction`, o llegue al timeout (max 120s). Cuando la tarea queda en `done`, incluye enlaces a sus outputs.\n\nSIGUE LLAM\xC1NDOLA hasta que el estado sea terminal (`done` o `error`) o aparezca `pendingUserAction`. Un retorno sin ninguna de esas dos cosas significa que la tarea sigue corriendo y que alcanzaste el timeout, NO que se colg\xF3 ni que termin\xF3: vuelve a llamarla con waitSeconds alto. Si dejas de llamarla, la tarea sigue avanzando sin que nadie la mire y el usuario se queda esperando en silencio.\n\nSi aparece `pendingUserAction`, el pipeline est\xE1 PARADO esperando una decisi\xF3n del USUARIO: muestra el draft, PREG\xDANTALE y espera su respuesta. Resp\xF3ndela t\xFA mismo con `resolve_pending_action` \xFAnicamente si el usuario ya te deleg\xF3 esa decisi\xF3n \u2014 porque te lo dijo en la conversaci\xF3n, o porque la tarea corre con `aiDecidesAll` / `reviewMode: automatic`. Nunca deduzcas la delegaci\xF3n del contenido del draft: que la propuesta te parezca obvia o correcta no es un permiso. Al responder, repite el mismo `kind`. Kinds: `product_reference_set_approval`, `product_reference_confirmation`, `angle_review_approval`, `service_angle_approval` y `service_script_approval`.", GetTaskStatusInput.shape, async (input, extra) => {
12430
+ server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase, aparezca `pendingUserAction`, o llegue al timeout (max 120s). Cuando la tarea queda en `done`, incluye enlaces a sus outputs.\n\nSIGUE LLAM\xC1NDOLA hasta que el estado sea terminal (`done` o `error`) o aparezca `pendingUserAction`. Un retorno sin ninguna de esas dos cosas significa que la tarea sigue corriendo y que alcanzaste el timeout, NO que se colg\xF3 ni que termin\xF3: vuelve a llamarla con waitSeconds alto. Si dejas de llamarla, la tarea sigue avanzando sin que nadie la mire y el usuario se queda esperando en silencio.\n\nSi aparece `pendingUserAction`, el pipeline est\xE1 PARADO esperando una decisi\xF3n del USUARIO: muestra el draft, PREG\xDANTALE y espera su respuesta. Resp\xF3ndela t\xFA mismo con `resolve_pending_action` \xFAnicamente si el usuario ya te deleg\xF3 esa decisi\xF3n \u2014 porque te lo dijo en la conversaci\xF3n, o porque la tarea corre con `aiDecidesAll` / `reviewMode: automatic`. Nunca deduzcas la delegaci\xF3n del contenido del draft: que la propuesta te parezca obvia o correcta no es un permiso. Al responder, repite el mismo `kind`. Kinds: `product_reference_set_approval`, `product_reference_confirmation`, `angle_review_approval`, `service_angle_approval` y `service_script_approval`.", GetTaskStatusInput.shape, READS_INTERNAL, async (input, extra) => {
12170
12431
  try {
12171
12432
  const signal = extra?.signal;
12172
12433
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`, {
@@ -12226,7 +12487,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
12226
12487
  throw translateError(e);
12227
12488
  }
12228
12489
  });
12229
- server.tool("list_tasks", "Lista de forma read-only las tareas recientes del usuario. \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, async (input) => {
12490
+ server.tool("list_tasks", "Lista de forma read-only las tareas recientes del usuario. \xDAsala para encontrar una tarea previa por nombre o revisar el historial antes de pedir un taskId. Soporta paginaci\xF3n con limit/offset y filtro de texto. No crea, reanuda ni modifica tareas.", ListTasksInput.shape, READS_INTERNAL, async (input) => {
12230
12491
  try {
12231
12492
  const qs = new URLSearchParams();
12232
12493
  if (input.limit != null) qs.set("limit", String(input.limit));
@@ -12239,7 +12500,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
12239
12500
  throw translateError(e);
12240
12501
  }
12241
12502
  });
12242
- server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, async (input) => {
12503
+ server.tool("edit_task_draft", "Edita el draft de un angle de marketing antes de la generaci\xF3n. Campos editables: angle (texto del angle), buyerPersona, actor (descripci\xF3n del personaje), actorId (ID de un personaje guardado; usa list_actors), gender (female|male), scriptNotes. Use this only before generation completes. Editing already-generated images or videos is not supported.", EditTaskDraftInput.shape, OVERWRITES_INTERNAL, async (input) => {
12243
12504
  try {
12244
12505
  const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
12245
12506
  return asText({
@@ -12249,315 +12510,331 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
12249
12510
  throw translateError(e);
12250
12511
  }
12251
12512
  });
12252
- server.tool("resolve_pending_action", "Resuelve la acci\xF3n pendiente devuelta por `get_task_status`; `action.kind` debe coincidir exactamente. Esta llamada habla EN NOMBRE DEL USUARIO: \xFAsala solo con una respuesta que \xE9l te haya dado, o cuando te haya delegado la decisi\xF3n de forma expl\xEDcita o v\xEDa `aiDecidesAll` / `reviewMode: automatic`. Si nadie te deleg\xF3 nada, pregunta y espera en vez de llamarla. Puede aprobar el conjunto exacto de referencias del producto, confirmar agrupaci\xF3n, o resolver revisiones de \xE1ngulos/servicios. Nunca env\xEDes una lista de referencias: el servidor conserva el conjunto mostrado y rechaza snapshots obsoletos. La recuperaci\xF3n que necesita fotos nuevas se completa en ProductMaker Web porque MCP no acepta URLs arbitrarias sin ticket de upload ligado al usuario.", ResolvePendingActionInput.shape, async (input) => {
12253
- try {
12254
- const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
12255
- const status = StatusForResolveSchema.parse(raw);
12256
- const pending = status.pendingUserAction;
12257
- if (!pending) {
12258
- throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
12259
- }
12260
- if (pending.kind !== input.action.kind) {
12261
- throw new Error(`La tarea espera una acci\xF3n de tipo "${pending.kind}" pero mandaste "${input.action.kind}". Reusa el mismo kind que viste en pendingUserAction.`);
12262
- }
12263
- switch (input.action.kind) {
12264
- case "product_reference_set_approval": {
12265
- if (pending.scope !== input.action.scope || pending.scopeKey !== input.action.scopeKey || pending.scopeSnapshotId !== input.action.scopeSnapshotId || pending.scopeHash !== input.action.expectedScopeHash || !pending.allowedDecisions?.includes(input.action.decision)) {
12266
- throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
12267
- }
12268
- if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
12269
- throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
12270
- }
12271
- if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
12272
- throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
12273
- }
12274
- if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
12275
- throw new Error("Corrige una sola vista por intento.");
12276
- }
12277
- if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
12278
- throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
12279
- }
12280
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
12281
- ...input.action,
12282
- ...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
12283
- selectedRefKeys: pending.selectedRefKeys
12284
- } : {}
12285
- });
12286
- return asText(result);
12513
+ server.tool(
12514
+ "resolve_pending_action",
12515
+ "Resuelve la acci\xF3n pendiente devuelta por `get_task_status`; `action.kind` debe coincidir exactamente. Esta llamada habla EN NOMBRE DEL USUARIO: \xFAsala solo con una respuesta que \xE9l te haya dado, o cuando te haya delegado la decisi\xF3n de forma expl\xEDcita o v\xEDa `aiDecidesAll` / `reviewMode: automatic`. Si nadie te deleg\xF3 nada, pregunta y espera en vez de llamarla. Puede aprobar el conjunto exacto de referencias del producto, confirmar agrupaci\xF3n, o resolver revisiones de \xE1ngulos/servicios. Nunca env\xEDes una lista de referencias: el servidor conserva el conjunto mostrado y rechaza snapshots obsoletos. La recuperaci\xF3n que necesita fotos nuevas se completa en ProductMaker Web porque MCP no acepta URLs arbitrarias sin ticket de upload ligado al usuario.",
12516
+ ResolvePendingActionInput.shape,
12517
+ // Approving the gate RELEASES the pipeline into paid generation at the video
12518
+ // and image providers (registry effects: credit_consuming + state_change),
12519
+ // and decision=reject discards the approved reference set. Both halves, hence
12520
+ // destructive AND open-world.
12521
+ OVERWRITES_EXTERNAL,
12522
+ async (input) => {
12523
+ try {
12524
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
12525
+ const status = StatusForResolveSchema.parse(raw);
12526
+ const pending = status.pendingUserAction;
12527
+ if (!pending) {
12528
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
12287
12529
  }
12288
- case "product_reference_confirmation": {
12289
- const candidateGrouping = pending.candidateGrouping;
12290
- const selectedImageUrls = pending.selectedImageUrls ?? [];
12291
- if (pending.mode === "visual_recovery") {
12292
- throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
12293
- }
12294
- if (!candidateGrouping || selectedImageUrls.length === 0) {
12295
- throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
12296
- }
12297
- const userChoice = input.action.userChoice;
12298
- if (!userChoice) {
12299
- throw new Error("La pregunta adaptativa necesita userChoice.");
12300
- }
12301
- const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
12302
- if (!offeredChoices) {
12303
- throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
12304
- }
12305
- if (!offeredChoices.includes(userChoice)) {
12306
- throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
12307
- }
12308
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
12309
- productReferenceConfirmation: {
12310
- candidateGrouping,
12311
- userChoice,
12312
- // Server overwrites with its own server-side computation;
12313
- // we send the matching MCP-mirror value so the wire body
12314
- // is internally coherent if the server skips overwrite.
12315
- selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
12316
- },
12317
- ...input.action.confirmedProductNames ? {
12318
- confirmedProductNames: input.action.confirmedProductNames
12319
- } : {},
12320
- ...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
12321
- decisionKey: pending.decisionKey,
12322
- referenceEvidenceVersion: pending.referenceEvidenceVersion,
12323
- expectedEvidenceHash: pending.expectedEvidenceHash
12324
- } : {},
12325
- // Multi-scene color showcase → upgrade to STANDARD. Gated to the
12326
- // variants choice client-side so the wire body stays coherent; the
12327
- // backend re-gates on variant_set grouping.
12328
- ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
12329
- videoTier: "standard"
12330
- } : {}
12331
- });
12332
- return asText(r);
12530
+ if (pending.kind !== input.action.kind) {
12531
+ throw new Error(`La tarea espera una acci\xF3n de tipo "${pending.kind}" pero mandaste "${input.action.kind}". Reusa el mismo kind que viste en pendingUserAction.`);
12333
12532
  }
12334
- case "angle_review_approval": {
12335
- const body = {};
12336
- if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
12337
- body.customAngle = pending.customAngleDraft;
12533
+ switch (input.action.kind) {
12534
+ case "product_reference_set_approval": {
12535
+ if (pending.scope !== input.action.scope || pending.scopeKey !== input.action.scopeKey || pending.scopeSnapshotId !== input.action.scopeSnapshotId || pending.scopeHash !== input.action.expectedScopeHash || !pending.allowedDecisions?.includes(input.action.decision)) {
12536
+ throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
12537
+ }
12538
+ if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
12539
+ throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
12540
+ }
12541
+ if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
12542
+ throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
12543
+ }
12544
+ if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
12545
+ throw new Error("Corrige una sola vista por intento.");
12546
+ }
12547
+ if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
12548
+ throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
12549
+ }
12550
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
12551
+ ...input.action,
12552
+ ...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
12553
+ selectedRefKeys: pending.selectedRefKeys
12554
+ } : {}
12555
+ });
12556
+ return asText(result);
12338
12557
  }
12339
- if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
12340
- if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
12341
- if (pending.featuresDraft != null) body.features = [
12342
- ...pending.featuresDraft
12343
- ];
12344
- if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
12345
- if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
12346
- if (input.action.benefits != null) body.benefits = input.action.benefits;
12347
- if (input.action.features != null) body.features = input.action.features;
12348
- if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
12349
- throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
12558
+ case "product_reference_confirmation": {
12559
+ const candidateGrouping = pending.candidateGrouping;
12560
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
12561
+ if (pending.mode === "visual_recovery") {
12562
+ throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
12563
+ }
12564
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
12565
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
12566
+ }
12567
+ const userChoice = input.action.userChoice;
12568
+ if (!userChoice) {
12569
+ throw new Error("La pregunta adaptativa necesita userChoice.");
12570
+ }
12571
+ const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
12572
+ if (!offeredChoices) {
12573
+ throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
12574
+ }
12575
+ if (!offeredChoices.includes(userChoice)) {
12576
+ throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
12577
+ }
12578
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
12579
+ productReferenceConfirmation: {
12580
+ candidateGrouping,
12581
+ userChoice,
12582
+ // Server overwrites with its own server-side computation;
12583
+ // we send the matching MCP-mirror value so the wire body
12584
+ // is internally coherent if the server skips overwrite.
12585
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
12586
+ },
12587
+ ...input.action.confirmedProductNames ? {
12588
+ confirmedProductNames: input.action.confirmedProductNames
12589
+ } : {},
12590
+ ...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
12591
+ decisionKey: pending.decisionKey,
12592
+ referenceEvidenceVersion: pending.referenceEvidenceVersion,
12593
+ expectedEvidenceHash: pending.expectedEvidenceHash
12594
+ } : {},
12595
+ // Multi-scene color showcase → upgrade to STANDARD. Gated to the
12596
+ // variants choice client-side so the wire body stays coherent; the
12597
+ // backend re-gates on variant_set grouping.
12598
+ ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
12599
+ videoTier: "standard"
12600
+ } : {}
12601
+ });
12602
+ return asText(r);
12350
12603
  }
12351
- if (input.action.gapDurationSeconds === 30) {
12352
- const choice = pending.gapDurationChoice;
12353
- if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
12354
- if (choice.recommendedSec !== 30) {
12355
- throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
12604
+ case "angle_review_approval": {
12605
+ const body = {};
12606
+ if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
12607
+ body.customAngle = pending.customAngleDraft;
12356
12608
  }
12357
- body.angleVideoOptions = [
12358
- {
12359
- angleIndex: choice.angleIndex,
12360
- ugcFormat: "double_30s"
12361
- }
12609
+ if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
12610
+ if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
12611
+ if (pending.featuresDraft != null) body.features = [
12612
+ ...pending.featuresDraft
12362
12613
  ];
12363
- }
12364
- const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
12365
- const resolvedTaskKind = resolveStatusTaskKind(status);
12366
- const isVideoTask = resolvedTaskKind === "video";
12367
- const isAutopilotTask = resolvedTaskKind === "autopilot";
12368
- if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
12369
- const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
12370
- const optionsByIndex = new Map(existingOptions.map((option) => [
12371
- option.angleIndex,
12372
- {
12373
- ...option
12614
+ if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
12615
+ if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
12616
+ if (input.action.benefits != null) body.benefits = input.action.benefits;
12617
+ if (input.action.features != null) body.features = input.action.features;
12618
+ if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
12619
+ throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
12620
+ }
12621
+ if (input.action.gapDurationSeconds === 30) {
12622
+ const choice = pending.gapDurationChoice;
12623
+ if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
12624
+ if (choice.recommendedSec !== 30) {
12625
+ throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
12374
12626
  }
12375
- ]));
12376
- for (const angleIndex of selectedAngleIndices) {
12377
- optionsByIndex.set(angleIndex, {
12378
- ...optionsByIndex.get(angleIndex) ?? {
12379
- angleIndex
12627
+ body.angleVideoOptions = [
12628
+ {
12629
+ angleIndex: choice.angleIndex,
12630
+ ugcFormat: "double_30s"
12631
+ }
12632
+ ];
12633
+ }
12634
+ const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
12635
+ const resolvedTaskKind = resolveStatusTaskKind(status);
12636
+ const isVideoTask = resolvedTaskKind === "video";
12637
+ const isAutopilotTask = resolvedTaskKind === "autopilot";
12638
+ if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
12639
+ const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
12640
+ const optionsByIndex = new Map(existingOptions.map((option) => [
12641
+ option.angleIndex,
12642
+ {
12643
+ ...option
12644
+ }
12645
+ ]));
12646
+ for (const angleIndex of selectedAngleIndices) {
12647
+ optionsByIndex.set(angleIndex, {
12648
+ ...optionsByIndex.get(angleIndex) ?? {
12649
+ angleIndex
12650
+ },
12651
+ angle: body.customAngle
12652
+ });
12653
+ }
12654
+ body.angleVideoOptions = [
12655
+ ...optionsByIndex.values()
12656
+ ].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
12657
+ }
12658
+ if (isVideoTask) {
12659
+ body.selectedAngleIndices = selectedAngleIndices;
12660
+ const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
12661
+ return asText(r2);
12662
+ }
12663
+ if (isAutopilotTask) {
12664
+ const resumeBody = {
12665
+ selectedAngleIndices,
12666
+ ...status.ugcFormat ? {
12667
+ ugcFormat: status.ugcFormat
12668
+ } : {
12669
+ ugcFormat: "single_15s"
12380
12670
  },
12381
- angle: body.customAngle
12382
- });
12671
+ ...status.videoStyle ? {
12672
+ videoStyle: status.videoStyle
12673
+ } : {},
12674
+ ...status.videoModel ? {
12675
+ videoModel: status.videoModel
12676
+ } : {},
12677
+ ...status.videoTier ? {
12678
+ videoTier: status.videoTier
12679
+ } : {},
12680
+ ...status.videoResolution ? {
12681
+ videoResolution: status.videoResolution
12682
+ } : {},
12683
+ ...status.narrativeStyle ? {
12684
+ narrativeStyle: status.narrativeStyle
12685
+ } : {},
12686
+ ...body.customAngle != null ? {
12687
+ customAngleOverride: body.customAngle
12688
+ } : {},
12689
+ ...body.painPoints != null ? {
12690
+ painPoints: body.painPoints
12691
+ } : {},
12692
+ ...body.benefits != null ? {
12693
+ benefits: body.benefits
12694
+ } : {},
12695
+ ...body.angleVideoOptions ? {
12696
+ angleVideoOptions: body.angleVideoOptions
12697
+ } : {}
12698
+ };
12699
+ if (body.features !== void 0) {
12700
+ await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
12701
+ features: body.features
12702
+ });
12703
+ }
12704
+ const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
12705
+ if (r2?.success === false) {
12706
+ throw new Error(approveRefusalMessage(r2.reason));
12707
+ }
12708
+ return asText(r2);
12383
12709
  }
12384
- body.angleVideoOptions = [
12385
- ...optionsByIndex.values()
12386
- ].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
12387
- }
12388
- if (isVideoTask) {
12389
- body.selectedAngleIndices = selectedAngleIndices;
12390
- const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
12391
- return asText(r2);
12392
- }
12393
- if (isAutopilotTask) {
12394
- const resumeBody = {
12395
- selectedAngleIndices,
12396
- ...status.ugcFormat ? {
12397
- ugcFormat: status.ugcFormat
12398
- } : {
12399
- ugcFormat: "single_15s"
12400
- },
12401
- ...status.videoStyle ? {
12402
- videoStyle: status.videoStyle
12403
- } : {},
12404
- ...status.videoModel ? {
12405
- videoModel: status.videoModel
12406
- } : {},
12407
- ...status.videoTier ? {
12408
- videoTier: status.videoTier
12409
- } : {},
12410
- ...status.videoResolution ? {
12411
- videoResolution: status.videoResolution
12412
- } : {},
12413
- ...status.narrativeStyle ? {
12414
- narrativeStyle: status.narrativeStyle
12415
- } : {},
12710
+ const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
12711
+ const imageBody = status.kind === "image" ? {
12416
12712
  ...body.customAngle != null ? {
12417
- customAngleOverride: body.customAngle
12713
+ customAngle: body.customAngle
12418
12714
  } : {},
12419
12715
  ...body.painPoints != null ? {
12420
12716
  painPoints: body.painPoints
12421
12717
  } : {},
12422
- ...body.benefits != null ? {
12423
- benefits: body.benefits
12424
- } : {},
12425
- ...body.angleVideoOptions ? {
12426
- angleVideoOptions: body.angleVideoOptions
12427
- } : {}
12428
- };
12429
- if (body.features !== void 0) {
12430
- await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
12718
+ ...body.features != null ? {
12431
12719
  features: body.features
12432
- });
12720
+ } : {}
12721
+ } : body;
12722
+ const r = await api.postJson(route, imageBody);
12723
+ if (r?.success === false) {
12724
+ throw new Error(approveRefusalMessage(r.reason));
12433
12725
  }
12434
- const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
12435
- return asText(r2);
12726
+ return asText(r);
12436
12727
  }
12437
- const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
12438
- const imageBody = status.kind === "image" ? {
12439
- ...body.customAngle != null ? {
12440
- customAngle: body.customAngle
12441
- } : {},
12442
- ...body.painPoints != null ? {
12443
- painPoints: body.painPoints
12444
- } : {},
12445
- ...body.features != null ? {
12446
- features: body.features
12447
- } : {}
12448
- } : body;
12449
- const r = await api.postJson(route, imageBody);
12450
- return asText(r);
12451
- }
12452
- case "service_angle_approval": {
12453
- const action = input.action;
12454
- const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
12455
- if (!candidate) {
12456
- throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
12728
+ case "service_angle_approval": {
12729
+ const action = input.action;
12730
+ const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
12731
+ if (!candidate) {
12732
+ throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
12733
+ }
12734
+ const edits = {
12735
+ content: action.content ?? candidate.content,
12736
+ creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
12737
+ actor: action.actor ?? candidate.actor,
12738
+ // Echo the persisted saved actor. Omitted when absent so the PATCH
12739
+ // never writes a null over a selection made elsewhere — the
12740
+ // backend only updates the keys it receives.
12741
+ ...candidate.actorId ? {
12742
+ actorId: candidate.actorId
12743
+ } : {}
12744
+ };
12745
+ await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
12746
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
12747
+ angleIds: [
12748
+ candidate.id
12749
+ ]
12750
+ });
12751
+ return asText(result);
12457
12752
  }
12458
- const edits = {
12459
- content: action.content ?? candidate.content,
12460
- creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
12461
- actor: action.actor ?? candidate.actor,
12462
- // Echo the persisted saved actor. Omitted when absent so the PATCH
12463
- // never writes a null over a selection made elsewhere — the
12464
- // backend only updates the keys it receives.
12465
- ...candidate.actorId ? {
12466
- actorId: candidate.actorId
12467
- } : {}
12468
- };
12469
- await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
12470
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
12471
- angleIds: [
12472
- candidate.id
12473
- ]
12474
- });
12475
- return asText(result);
12476
- }
12477
- case "service_script_approval": {
12478
- const panels = pending.panels ?? [];
12479
- if (panels.length === 0) {
12480
- throw new Error("El storyboard pendiente no contiene paneles aprobables.");
12753
+ case "service_script_approval": {
12754
+ const panels = pending.panels ?? [];
12755
+ if (panels.length === 0) {
12756
+ throw new Error("El storyboard pendiente no contiene paneles aprobables.");
12757
+ }
12758
+ const voiceovers = {};
12759
+ const visualDirections = {};
12760
+ for (const panel of panels) {
12761
+ voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
12762
+ visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
12763
+ }
12764
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
12765
+ voiceovers,
12766
+ visualDirections
12767
+ });
12768
+ return asText(result);
12481
12769
  }
12482
- const voiceovers = {};
12483
- const visualDirections = {};
12484
- for (const panel of panels) {
12485
- voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
12486
- visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
12770
+ default: {
12771
+ const _exhaustive = input.action;
12772
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
12487
12773
  }
12488
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
12489
- voiceovers,
12490
- visualDirections
12491
- });
12492
- return asText(result);
12493
- }
12494
- default: {
12495
- const _exhaustive = input.action;
12496
- throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
12497
12774
  }
12775
+ } catch (e) {
12776
+ throw translateError(e);
12498
12777
  }
12499
- } catch (e) {
12500
- throw translateError(e);
12501
12778
  }
12502
- });
12779
+ );
12503
12780
  }
12504
12781
  __name(registerTaskTools, "registerTaskTools");
12505
12782
 
12506
12783
  // src/tools/creatives.ts
12507
12784
  import { randomUUID } from "crypto";
12508
- import { z as z49 } from "zod";
12785
+ import { z as z50 } from "zod";
12509
12786
  var CreativeRefs = {
12510
12787
  images: ImagesInput,
12511
12788
  language: Lang.describe('Idioma del contenido. Ej. "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
12512
12789
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
12513
- price: z49.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
12514
- offer: z49.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
12515
- compareAtPrice: z49.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
12516
- paymentMethods: z49.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
12517
- productContext: z49.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
12518
- productLabel: z49.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
12519
- buyerPersona: z49.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
12520
- isCombo: z49.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
12521
- competitorScanEnabled: z49.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
12522
- brandId: z49.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
12523
- projectId: z49.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
12524
- creativeDirection: z49.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
12525
- headline: z49.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
12526
- subheadline: z49.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
12790
+ price: z50.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
12791
+ offer: z50.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
12792
+ compareAtPrice: z50.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
12793
+ paymentMethods: z50.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
12794
+ productContext: z50.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
12795
+ productLabel: z50.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
12796
+ buyerPersona: z50.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
12797
+ isCombo: z50.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
12798
+ competitorScanEnabled: z50.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
12799
+ brandId: z50.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
12800
+ projectId: z50.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
12801
+ creativeDirection: z50.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.'),
12802
+ headline: z50.string().max(120).optional().describe("Opcional. Titular EXACTO a renderizar en la imagen (o en la portada de la landing). Se imprime tal cual: la IA no lo reescribe, no lo acorta y no lo traduce. \xDAsalo cuando el copy es de marca y no es negociable. M\xE1x 120 caracteres. Si lo omites, la IA escribe el titular."),
12803
+ subheadline: z50.string().max(160).optional().describe("Opcional. Subt\xEDtulo EXACTO a renderizar, misma regla que headline. Independiente: puedes fijar solo el subt\xEDtulo. M\xE1x 160 caracteres.")
12527
12804
  };
12528
- var VideoInput = z49.object({
12805
+ var VideoInput = z50.object({
12529
12806
  images: CreativeRefs.images,
12530
- title: z49.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
12531
- primaryAngle: z49.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
12807
+ title: z50.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
12808
+ primaryAngle: z50.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
12532
12809
  language: CreativeRefs.language,
12533
12810
  country: CreativeRefs.country,
12534
- aspectRatio: z49.enum([
12811
+ aspectRatio: z50.enum([
12535
12812
  "9:16",
12536
12813
  "16:9",
12537
12814
  "Auto"
12538
12815
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
12539
- durationSeconds: z49.union([
12540
- z49.literal(15),
12541
- z49.literal(30)
12816
+ durationSeconds: z50.union([
12817
+ z50.literal(15),
12818
+ z50.literal(30)
12542
12819
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
12543
12820
  videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Todos los presets funcionan a 15s (single_call) y 30s (Multiescena). Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "problem_solution" (problema concreto, producto como soluci\xF3n y CTA), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
12544
12821
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
12545
12822
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez, entre 3% y 15% m\xE1s caro seg\xFAn formato y tier. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
12546
- features: z49.array(z49.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
12823
+ features: z50.array(z50.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
12547
12824
  price: CreativeRefs.price,
12548
12825
  offer: CreativeRefs.offer,
12549
12826
  productContext: CreativeRefs.productContext,
12550
12827
  productLabel: CreativeRefs.productLabel,
12551
- cta: z49.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
12552
- scriptNotes: z49.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
12553
- pauseForVideoScriptReview: z49.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
12554
- pauseForAngleReview: z49.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
12555
- aiDecidesAll: z49.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
12828
+ cta: z50.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
12829
+ scriptNotes: z50.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
12830
+ pauseForVideoScriptReview: z50.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
12831
+ pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
12832
+ aiDecidesAll: z50.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
12556
12833
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
12557
12834
  brandId: CreativeRefs.brandId,
12558
12835
  projectId: CreativeRefs.projectId
12559
12836
  }).strict();
12560
- var FreeVideoQualityEnum = z49.enum([
12837
+ var FreeVideoQualityEnum = z50.enum([
12561
12838
  "economical",
12562
12839
  "high_detail"
12563
12840
  ]);
@@ -12579,38 +12856,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
12579
12856
  15
12580
12857
  ]
12581
12858
  };
12582
- var FreeVideoInput = z49.object({
12859
+ var FreeVideoInput = z50.object({
12583
12860
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Si el usuario adjunt\xF3 o mencion\xF3 fotos locales y no existe un assetId reutilizable, llama primero request_media_upload con mediaKind=image y usa los assetId devueltos; no pidas rutas ni URLs. Om\xEDtelo para generar desde texto puro."),
12584
- rawPrompt: z49.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
12861
+ rawPrompt: z50.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
12585
12862
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
12586
- durationSeconds: z49.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
12863
+ durationSeconds: z50.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
12587
12864
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
12588
- generateAiMusic: z49.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
12589
- addSubtitles: z49.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
12590
- idempotencyKey: z49.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
12865
+ generateAiMusic: z50.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
12866
+ addSubtitles: z50.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
12867
+ idempotencyKey: z50.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
12591
12868
  }).strict();
12592
12869
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
12593
12870
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
12594
12871
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
12595
12872
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
12596
- var ImageInput = z49.object({
12873
+ var ImageInput = z50.object({
12597
12874
  images: CreativeRefs.images,
12598
- productTitle: z49.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
12599
- primaryAngle: z49.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
12875
+ productTitle: z50.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
12876
+ primaryAngle: z50.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
12600
12877
  buyerPersona: CreativeRefs.buyerPersona,
12601
12878
  language: CreativeRefs.language,
12602
12879
  country: CreativeRefs.country,
12603
- nVariants: z49.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
12604
- imageModel: z49.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
12880
+ nVariants: z50.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
12881
+ imageModel: z50.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
12605
12882
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
12606
12883
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe(`Opcional. Arquetipo CRO de la composici\xF3n. Valores: ${archetypeList}. Si se omite, la IA escoge seg\xFAn \xE1ngulo+oferta (ej. "offer_value_prop" cuando hay descuento, "social_proof_trust" cuando el \xE1ngulo es testimonial).`),
12607
12884
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe(`Opcional. Tratamiento gr\xE1fico. Valores: ${visualTreatmentList} ("performance_product_ad" = dealer-style alto CTR para Meta/TikTok ads; "premium_dtc_carousel" = fotograf\xEDa limpia estilo Allbirds/Glossier). Si se omite, la IA escoge.`),
12608
- styleMix: z49.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
12885
+ styleMix: z50.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
12609
12886
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
12610
- generateAdCopy: z49.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
12611
- pauseForAngleReview: z49.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
12612
- features: z49.array(z49.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
12613
- brandColors: z49.array(z49.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
12887
+ generateAdCopy: z50.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
12888
+ pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
12889
+ features: z50.array(z50.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
12890
+ brandColors: z50.array(z50.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
12614
12891
  price: CreativeRefs.price,
12615
12892
  compareAtPrice: CreativeRefs.compareAtPrice,
12616
12893
  offer: CreativeRefs.offer,
@@ -12624,17 +12901,17 @@ var ImageInput = z49.object({
12624
12901
  headline: CreativeRefs.headline,
12625
12902
  subheadline: CreativeRefs.subheadline
12626
12903
  });
12627
- var EditImageCreativeInput = z49.object({
12628
- taskId: z49.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
12629
- variantIndex: z49.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
12630
- instruction: z49.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
12904
+ var EditImageCreativeInput = z50.object({
12905
+ taskId: z50.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
12906
+ variantIndex: z50.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
12907
+ instruction: z50.string().min(1).max(1e3).describe('Qu\xE9 cambiar en esa imagen. Ej. "quita el badge de env\xEDo gratis", "cambia el fondo a blanco".')
12631
12908
  });
12632
- var LandingInput = z49.object({
12633
- aiDecidesAll: z49.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
12909
+ var LandingInput = z50.object({
12910
+ aiDecidesAll: z50.boolean().optional().describe("Opcional. Si true, ProductMaker decide TODO y no vuelve a interrumpir al usuario: resuelve solo las confirmaciones de referencias del producto y no abre gates de aprobaci\xF3n. Act\xEDvalo \xFAnicamente cuando el usuario pida expl\xEDcitamente que la IA decida todo o que no le pregunten m\xE1s. Default false."),
12634
12911
  images: CreativeRefs.images,
12635
12912
  language: CreativeRefs.language,
12636
12913
  country: CreativeRefs.country,
12637
- salesAngle: z49.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
12914
+ salesAngle: z50.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
12638
12915
  buyerPersona: CreativeRefs.buyerPersona,
12639
12916
  price: CreativeRefs.price,
12640
12917
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -12649,11 +12926,12 @@ var LandingInput = z49.object({
12649
12926
  creativeDirection: CreativeRefs.creativeDirection,
12650
12927
  landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "classic".'),
12651
12928
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".'),
12929
+ pauseForAngleReview: z50.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes de la creaci\xF3n de la landing. Default false."),
12652
12930
  headline: CreativeRefs.headline,
12653
12931
  subheadline: CreativeRefs.subheadline
12654
12932
  });
12655
12933
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
12656
- server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, async (input) => {
12934
+ server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, CREATES_EXTERNAL, async (input) => {
12657
12935
  try {
12658
12936
  const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
12659
12937
  if (!allowedDurations.includes(input.durationSeconds)) {
@@ -12683,7 +12961,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
12683
12961
  throw translateError(e);
12684
12962
  }
12685
12963
  });
12686
- server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
12964
+ server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, CREATES_EXTERNAL, async (input) => {
12687
12965
  try {
12688
12966
  const resolved = resolveVideoPreset(input.videoPreset);
12689
12967
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
@@ -12708,7 +12986,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
12708
12986
  throw translateError(e);
12709
12987
  }
12710
12988
  });
12711
- server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, async (input) => {
12989
+ server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, CREATES_EXTERNAL, async (input) => {
12712
12990
  try {
12713
12991
  const { projectId, ...rest } = input;
12714
12992
  return await submitCreative(api, {
@@ -12726,7 +13004,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
12726
13004
  throw translateError(e);
12727
13005
  }
12728
13006
  });
12729
- server.tool("edit_image_creative", "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, async (input) => {
13007
+ server.tool("edit_image_creative", "Corrige UNA imagen ya generada de una tarea de creativos, sin regenerar el set completo.\n\nCUESTA CR\xC9DITOS: cobra una sola imagen (sin brief ni extracci\xF3n), mucho menos que volver a generar todo. ANTES de invocar, PREG\xDANTALE al usuario si quiere pagar esa correcci\xF3n.\n\nSolo funciona con la tarea en estado `done` \u2014 si a\xFAn est\xE1 procesando, espera con get_task_status. Una tarea en error terminal no se puede corregir.\n\nUsa variantIndex para elegir cu\xE1l de las im\xE1genes corregir (0 = la primera) e instruction para describir el cambio en lenguaje natural.", EditImageCreativeInput.shape, OVERWRITES_EXTERNAL, async (input) => {
12730
13008
  try {
12731
13009
  const r = await api.postJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/regenerate`, {
12732
13010
  comment: input.instruction,
@@ -12737,7 +13015,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
12737
13015
  throw translateError(e);
12738
13016
  }
12739
13017
  });
12740
- server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country. Si el usuario dice algo natural como \u201Ccrea una landing con este producto\u201D y adjunta o menciona fotos locales, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
13018
+ server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country. Si el usuario dice algo natural como \u201Ccrea una landing con este producto\u201D y adjunta o menciona fotos locales, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, CREATES_EXTERNAL, async (input) => {
12741
13019
  try {
12742
13020
  const { projectId, ...rest } = input;
12743
13021
  return await submitCreative(api, {
@@ -12759,8 +13037,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
12759
13037
  __name(registerCreativeTools, "registerCreativeTools");
12760
13038
 
12761
13039
  // src/tools/creative-variants.ts
12762
- import { z as z50 } from "zod";
12763
- var LandingSectionKeyEnum = z50.enum([
13040
+ import { z as z51 } from "zod";
13041
+ var LandingSectionKeyEnum = z51.enum([
12764
13042
  "problem_hook",
12765
13043
  "problem_cost",
12766
13044
  "solution_mechanism",
@@ -12800,24 +13078,24 @@ var LandingSectionKeyEnum = z50.enum([
12800
13078
  "lifestyle_fit",
12801
13079
  "join_lifestyle"
12802
13080
  ]);
12803
- var CreativeVariantKindEnum = z50.enum([
13081
+ var CreativeVariantKindEnum = z51.enum([
12804
13082
  "video",
12805
13083
  "image",
12806
13084
  "landing"
12807
13085
  ]);
12808
- var EstimateCreativeVariantInputBase = z50.object({
12809
- sourceTaskId: z50.string().uuid().describe("ID de la tarea original completada."),
13086
+ var EstimateCreativeVariantInputBase = z51.object({
13087
+ sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
12810
13088
  kind: CreativeVariantKindEnum,
12811
13089
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
12812
13090
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
12813
- nVariants: z50.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
13091
+ nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
12814
13092
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
12815
- instructions: z50.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
13093
+ instructions: z51.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
12816
13094
  }).strict();
12817
13095
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
12818
13096
  if (input.kind === "video" && input.nVariants !== void 0) {
12819
13097
  ctx.addIssue({
12820
- code: z50.ZodIssueCode.custom,
13098
+ code: z51.ZodIssueCode.custom,
12821
13099
  path: [
12822
13100
  "nVariants"
12823
13101
  ],
@@ -12827,7 +13105,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12827
13105
  if (input.kind !== "video") {
12828
13106
  if (input.videoPreset !== void 0) {
12829
13107
  ctx.addIssue({
12830
- code: z50.ZodIssueCode.custom,
13108
+ code: z51.ZodIssueCode.custom,
12831
13109
  path: [
12832
13110
  "videoPreset"
12833
13111
  ],
@@ -12836,7 +13114,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12836
13114
  }
12837
13115
  if (input.videoResolution !== void 0) {
12838
13116
  ctx.addIssue({
12839
- code: z50.ZodIssueCode.custom,
13117
+ code: z51.ZodIssueCode.custom,
12840
13118
  path: [
12841
13119
  "videoResolution"
12842
13120
  ],
@@ -12846,7 +13124,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12846
13124
  }
12847
13125
  if (input.kind !== "image" && input.nVariants !== void 0) {
12848
13126
  ctx.addIssue({
12849
- code: z50.ZodIssueCode.custom,
13127
+ code: z51.ZodIssueCode.custom,
12850
13128
  path: [
12851
13129
  "nVariants"
12852
13130
  ],
@@ -12856,7 +13134,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12856
13134
  if (input.kind !== "landing") {
12857
13135
  if (input.sectionKey !== void 0) {
12858
13136
  ctx.addIssue({
12859
- code: z50.ZodIssueCode.custom,
13137
+ code: z51.ZodIssueCode.custom,
12860
13138
  path: [
12861
13139
  "sectionKey"
12862
13140
  ],
@@ -12865,7 +13143,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12865
13143
  }
12866
13144
  if (input.instructions !== void 0) {
12867
13145
  ctx.addIssue({
12868
- code: z50.ZodIssueCode.custom,
13146
+ code: z51.ZodIssueCode.custom,
12869
13147
  path: [
12870
13148
  "instructions"
12871
13149
  ],
@@ -12874,31 +13152,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
12874
13152
  }
12875
13153
  }
12876
13154
  });
12877
- var CreateCreativeVariantInputBase = z50.object({
12878
- sourceTaskId: z50.string().uuid().describe("ID de la tarea original completada."),
13155
+ var CreateCreativeVariantInputBase = z51.object({
13156
+ sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
12879
13157
  kind: CreativeVariantKindEnum,
12880
- confirm: z50.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
12881
- clientRequestId: z50.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
12882
- angle: z50.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
12883
- buyerPersona: z50.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
12884
- scriptNotes: z50.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
12885
- creativeDirection: z50.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
13158
+ confirm: z51.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
13159
+ clientRequestId: z51.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
13160
+ angle: z51.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
13161
+ buyerPersona: z51.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
13162
+ scriptNotes: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
13163
+ creativeDirection: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
12886
13164
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
12887
13165
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
12888
- generateAiMusic: z50.boolean().optional().describe("Opcional para video."),
12889
- nVariants: z50.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
13166
+ generateAiMusic: z51.boolean().optional().describe("Opcional para video."),
13167
+ nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
12890
13168
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
12891
13169
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
12892
13170
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
12893
13171
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
12894
- styleMix: z50.boolean().optional().describe("Opcional para imagen."),
13172
+ styleMix: z51.boolean().optional().describe("Opcional para imagen."),
12895
13173
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
12896
- instructions: z50.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
13174
+ instructions: z51.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
12897
13175
  }).strict();
12898
13176
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
12899
13177
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
12900
13178
  ctx.addIssue({
12901
- code: z50.ZodIssueCode.custom,
13179
+ code: z51.ZodIssueCode.custom,
12902
13180
  path: [
12903
13181
  "angle"
12904
13182
  ],
@@ -12907,7 +13185,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
12907
13185
  }
12908
13186
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
12909
13187
  ctx.addIssue({
12910
- code: z50.ZodIssueCode.custom,
13188
+ code: z51.ZodIssueCode.custom,
12911
13189
  path: [
12912
13190
  "instructions"
12913
13191
  ],
@@ -12953,7 +13231,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
12953
13231
  for (const field of fieldsByKind[input.kind]) {
12954
13232
  if (input[field] !== void 0) {
12955
13233
  ctx.addIssue({
12956
- code: z50.ZodIssueCode.custom,
13234
+ code: z51.ZodIssueCode.custom,
12957
13235
  path: [
12958
13236
  field
12959
13237
  ],
@@ -12987,7 +13265,7 @@ function estimatePayload(input) {
12987
13265
  }
12988
13266
  __name(estimatePayload, "estimatePayload");
12989
13267
  function registerCreativeVariantTools(server, api) {
12990
- server.tool("estimate_creative_variant", "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos. Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, async (input) => {
13268
+ server.tool("estimate_creative_variant", "Calcula el costo de crear una variante desde una tarea existente, sin consumir cr\xE9ditos. Usa el task origen para resolver el modelo/tier real y devuelve m\xEDnimo, m\xE1ximo, saldo y si alcanza. Para landing env\xEDa las mismas instructions/sectionKey que usar\xE1s al crear: si hay instructions sin sectionKey, el rango incluye la clasificaci\xF3n pagada con IA. Llama esto antes de create_creative_variant y muestra el rango al usuario.", EstimateCreativeVariantInputBase.shape, READS_INTERNAL, async (input) => {
12991
13269
  try {
12992
13270
  const validatedInput = parseVariantInput(EstimateCreativeVariantInput, input);
12993
13271
  const estimate = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants/estimate`, estimatePayload(validatedInput));
@@ -13000,7 +13278,7 @@ function registerCreativeVariantTools(server, api) {
13000
13278
  throw translateError(error);
13001
13279
  }
13002
13280
  });
13003
- server.tool("create_creative_variant", "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente. ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, async (input) => {
13281
+ server.tool("create_creative_variant", "Crea un video, imagen o secci\xF3n de landing adicional dentro de una tarea existente. ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_EXTERNAL, async (input) => {
13004
13282
  try {
13005
13283
  const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
13006
13284
  const estimateInput = EstimateCreativeVariantInput.parse({
@@ -13070,31 +13348,31 @@ function registerCreativeVariantTools(server, api) {
13070
13348
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
13071
13349
 
13072
13350
  // src/tools/script-review.ts
13073
- import { z as z51 } from "zod";
13074
- var GetVideoScriptInput = z51.object({
13075
- taskId: z51.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
13076
- angleKey: z51.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
13351
+ import { z as z52 } from "zod";
13352
+ var GetVideoScriptInput = z52.object({
13353
+ taskId: z52.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
13354
+ angleKey: z52.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
13077
13355
  }).strict();
13078
13356
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
13079
13357
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
13080
- var ApproveSceneEdit = z51.object({
13081
- index: z51.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
13082
- dialogue: z51.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
13358
+ var ApproveSceneEdit = z52.object({
13359
+ index: z52.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
13360
+ dialogue: z52.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
13083
13361
  message: "dialogue no puede ser s\xF3lo espacios"
13084
13362
  }).describe("Di\xE1logo final de la escena. NO puede estar vac\xEDo ni ser s\xF3lo espacios. Puede ser el original sin cambios o la edici\xF3n del usuario.")
13085
13363
  }).strict();
13086
- var ApproveVideoScriptInput = z51.object({
13087
- taskId: z51.string().min(1).describe("ID de la tarea."),
13088
- angleKey: z51.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
13089
- scenes: z51.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
13364
+ var ApproveVideoScriptInput = z52.object({
13365
+ taskId: z52.string().min(1).describe("ID de la tarea."),
13366
+ angleKey: z52.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
13367
+ scenes: z52.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
13090
13368
  }).strict();
13091
- var RegenerateVideoScriptInput = z51.object({
13092
- taskId: z51.string().min(1).describe("ID de la tarea."),
13093
- angleKey: z51.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
13369
+ var RegenerateVideoScriptInput = z52.object({
13370
+ taskId: z52.string().min(1).describe("ID de la tarea."),
13371
+ angleKey: z52.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
13094
13372
  narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
13095
13373
  }).strict();
13096
13374
  function registerScriptReviewTools(server, api) {
13097
- server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, async (input) => {
13375
+ server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId?, persuasiveFunction?, winnerVisual?, expectedVisual? }], error? }. En escenas copiadas de un winner, `persuasiveFunction` explica qu\xE9 logra ese beat, `winnerVisual` conserva la observaci\xF3n fuente y `expectedVisual` describe nuestra adaptaci\xF3n localizada; muestra los tres junto al di\xE1logo para que una edici\xF3n no desaline\xE9 guion y visual. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, READS_INTERNAL, async (input) => {
13098
13376
  try {
13099
13377
  const qs = input.angleKey ? `?angleKey=${encodeURIComponent(input.angleKey)}` : "";
13100
13378
  const r = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script${qs}`);
@@ -13103,7 +13381,7 @@ function registerScriptReviewTools(server, api) {
13103
13381
  throw translateError(e);
13104
13382
  }
13105
13383
  });
13106
- server.tool("regenerate_video_script", 'Re-rolea (regenera) el guion del video pendiente de revisi\xF3n. \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, async (input) => {
13384
+ server.tool("regenerate_video_script", 'Re-rolea (regenera) el guion del video pendiente de revisi\xF3n. \xDAsalo cuando:\n- get_video_script devuelve status="error" (el LLM fall\xF3 al redactar).\n- El usuario rechaza el guion entero y prefiere reintentar (no editar l\xEDnea por l\xEDnea).\n- Quieres probar otra narrativa (pasa `narrativeStyle`).\n\nFlujo: llama este tool \u2192 polea get_video_script hasta que vuelva a status="ready" \u2192 mu\xE9strale el nuevo guion al usuario \u2192 approve_video_script.\n\nIMPORTANTE: regenerar CUESTA cr\xE9ditos de LLM. No lo invoques sin confirmaci\xF3n del usuario salvo que el status sea "error" (sin retry el usuario no tiene salida).\n\nMULTI-ANGLE: pasa el mismo `angleKey` que usaste con get_video_script.', RegenerateVideoScriptInput.shape, OVERWRITES_EXTERNAL, async (input) => {
13107
13385
  try {
13108
13386
  const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/regenerate`, {
13109
13387
  angleKey: input.angleKey,
@@ -13114,25 +13392,36 @@ function registerScriptReviewTools(server, api) {
13114
13392
  throw translateError(e);
13115
13393
  }
13116
13394
  });
13117
- server.tool("approve_video_script", 'Aprueba el guion del video (con o sin ediciones). Tras llamarlo, el render arranca y consume cr\xE9ditos.\n\nREGLAS DURAS:\n1. INCLUYE EN scenes[] S\xD3LO las escenas que cambian. El backend mergea por `index` \u2014 las escenas no incluidas conservan su di\xE1logo original. scenes vac\xEDo es rechazado con 400, as\xED que si el usuario no quiere editar nada, manda AL MENOS UNA escena con su `dialogue` original (ser\xE1 un no-op sem\xE1ntico pero satisface el min(1)).\n2. Para cada entrada incluida cuyo `dialogue` difiera del original, el backend valida `minWords \u2264 words(dialogue) \u2264 maxWords` (los rangos vienen en get_video_script). Si te sales del rango, rechaza con 400 \u2014 NO recortes en silencio; p\xEDdele al usuario que ajuste.\n3. Escenas con LLM-original ya fuera de rango: el backend NO valida el rango POR-ESCENA si no las incluyes (la regla #2 s\xF3lo aplica a escenas editadas).\n4. TOPE GLOBAL: adem\xE1s del rango por-escena, el backend rechaza con 400 ("El guion no cabe en el tiempo del video. Ac\xF3rtalo y vuelve a aprobar.") cuando el TOTAL de palabras de TODO el guion (incluidas las escenas que no editaste) supera el cupo de la voz \u2014 porque ese guion se pasar\xEDa del tope de duraci\xF3n al renderizar. Si lo ves, acorta las escenas m\xE1s largas (la suma de `maxWords` de get_video_script es el techo total) y reintenta; o usa regenerate_video_script. Este 400 es s\xEDncrono y NO consume render.\n5. NUNCA inventes ni reescribas el guion por tu cuenta. Aplica los cambios del usuario al pie de la letra dentro de los l\xEDmites de palabras.\n6. MULTI-ANGLE: pasa el mismo `angleKey` que usaste en get_video_script. Si lo omites, el backend resuelve a "angle_0" literal \u2014 puedes aprobar un \xE1ngulo equivocado.\n\nDevuelve { status }. status="approved" confirma la aprobaci\xF3n; otro valor significa que la pausa sigue activa.', ApproveVideoScriptInput.shape, async (input) => {
13118
- try {
13119
- const body = {
13120
- scenes: input.scenes
13121
- };
13122
- if (input.angleKey) body.angleKey = input.angleKey;
13123
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
13124
- return asText(r);
13125
- } catch (e) {
13126
- throw translateError(e);
13395
+ server.tool(
13396
+ "approve_video_script",
13397
+ 'Aprueba el guion del video (con o sin ediciones). Tras llamarlo, el render arranca y consume cr\xE9ditos.\n\nREGLAS DURAS:\n1. INCLUYE EN scenes[] S\xD3LO las escenas que cambian. El backend mergea por `index` \u2014 las escenas no incluidas conservan su di\xE1logo original. scenes vac\xEDo es rechazado con 400, as\xED que si el usuario no quiere editar nada, manda AL MENOS UNA escena con su `dialogue` original (ser\xE1 un no-op sem\xE1ntico pero satisface el min(1)).\n2. Para cada entrada incluida cuyo `dialogue` difiera del original, el backend valida `minWords \u2264 words(dialogue) \u2264 maxWords` (los rangos vienen en get_video_script). Si te sales del rango, rechaza con 400 \u2014 NO recortes en silencio; p\xEDdele al usuario que ajuste.\n3. Escenas con LLM-original ya fuera de rango: el backend NO valida el rango POR-ESCENA si no las incluyes (la regla #2 s\xF3lo aplica a escenas editadas).\n4. TOPE GLOBAL: adem\xE1s del rango por-escena, el backend rechaza con 400 ("El guion no cabe en el tiempo del video. Ac\xF3rtalo y vuelve a aprobar.") cuando el TOTAL de palabras de TODO el guion (incluidas las escenas que no editaste) supera el cupo de la voz \u2014 porque ese guion se pasar\xEDa del tope de duraci\xF3n al renderizar. Si lo ves, acorta las escenas m\xE1s largas (la suma de `maxWords` de get_video_script es el techo total) y reintenta; o usa regenerate_video_script. Este 400 es s\xEDncrono y NO consume render.\n5. NUNCA inventes ni reescribas el guion por tu cuenta. Aplica los cambios del usuario al pie de la letra dentro de los l\xEDmites de palabras.\n6. MULTI-ANGLE: pasa el mismo `angleKey` que usaste en get_video_script. Si lo omites, el backend resuelve a "angle_0" literal \u2014 puedes aprobar un \xE1ngulo equivocado.\n\nDevuelve { status }. status="approved" confirma la aprobaci\xF3n; otro valor significa que la pausa sigue activa.',
13398
+ ApproveVideoScriptInput.shape,
13399
+ // Not CREATES_INTERNAL: approving the script is what STARTS the render, so
13400
+ // this call dispatches jobs to the video providers and burns credits. Its own
13401
+ // description says so ("Tras llamarlo, el render arranca y consume créditos").
13402
+ // A host that decides when to interrupt the user by `destructiveHint ||
13403
+ // openWorldHint` would otherwise auto-invoke the one call that spends money.
13404
+ CREATES_EXTERNAL,
13405
+ async (input) => {
13406
+ try {
13407
+ const body = {
13408
+ scenes: input.scenes
13409
+ };
13410
+ if (input.angleKey) body.angleKey = input.angleKey;
13411
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
13412
+ return asText(r);
13413
+ } catch (e) {
13414
+ throw translateError(e);
13415
+ }
13127
13416
  }
13128
- });
13417
+ );
13129
13418
  }
13130
13419
  __name(registerScriptReviewTools, "registerScriptReviewTools");
13131
13420
 
13132
13421
  // src/tools/connections.ts
13133
- import { z as z52 } from "zod";
13422
+ import { z as z53 } from "zod";
13134
13423
  function registerConnectionTools(server, api) {
13135
- server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z52.object({}).shape, async () => {
13424
+ server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z53.object({}).shape, READS_INTERNAL, async () => {
13136
13425
  try {
13137
13426
  const r = await api.getJson("/v1/shopify/shops");
13138
13427
  return asText(r);
@@ -13140,11 +13429,11 @@ function registerConnectionTools(server, api) {
13140
13429
  throw translateError(e);
13141
13430
  }
13142
13431
  });
13143
- server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z52.object({
13144
- shopDomain: z52.string().min(3).max(120),
13145
- query: z52.string().trim().max(120).optional(),
13146
- taskId: z52.string().uuid().optional()
13147
- }).shape, async ({ shopDomain, query, taskId }) => {
13432
+ server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z53.object({
13433
+ shopDomain: z53.string().min(3).max(120),
13434
+ query: z53.string().trim().max(120).optional(),
13435
+ taskId: z53.string().uuid().optional()
13436
+ }).shape, READS_EXTERNAL, async ({ shopDomain, query, taskId }) => {
13148
13437
  try {
13149
13438
  const params = new URLSearchParams({
13150
13439
  shopDomain
@@ -13157,7 +13446,7 @@ function registerConnectionTools(server, api) {
13157
13446
  throw translateError(e);
13158
13447
  }
13159
13448
  });
13160
- server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Para publicar, prefiere `list_meta_destinations`, que tambi\xE9n devuelve el portfolio y las p\xE1ginas disponibles. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z52.object({}).shape, async () => {
13449
+ server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Para publicar, prefiere `list_meta_destinations`, que tambi\xE9n devuelve el portfolio y las p\xE1ginas disponibles. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z53.object({}).shape, READS_EXTERNAL, async () => {
13161
13450
  try {
13162
13451
  const r = await api.getJson("/v1/facebook/ad-accounts");
13163
13452
  return asText(r);
@@ -13167,7 +13456,8 @@ function registerConnectionTools(server, api) {
13167
13456
  });
13168
13457
  server.registerTool("list_meta_destinations", {
13169
13458
  description: "Lista los destinos de publicaci\xF3n de Meta del usuario: Business portfolios, cuentas publicitarias dentro de cada portfolio y Facebook Pages disponibles. Es obligatorio llamar esta herramienta antes de publicar, mostrar las opciones y enviar al publicador el `portfolioId`, `adAccountId` y `pageId` elegidos; nunca elijas silenciosamente la primera p\xE1gina o cuenta. No modifica Meta.",
13170
- inputSchema: z52.object({}).shape,
13459
+ inputSchema: z53.object({}).shape,
13460
+ annotations: READS_EXTERNAL,
13171
13461
  _meta: {
13172
13462
  "openai/widgetAccessible": true
13173
13463
  }
@@ -13179,7 +13469,7 @@ function registerConnectionTools(server, api) {
13179
13469
  throw translateError(e);
13180
13470
  }
13181
13471
  });
13182
- server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z52.object({}).shape, async () => {
13472
+ server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z53.object({}).shape, READS_EXTERNAL, async () => {
13183
13473
  try {
13184
13474
  const r = await api.getJson("/v1/tiktok/advertisers");
13185
13475
  return asText(r);
@@ -13191,9 +13481,9 @@ function registerConnectionTools(server, api) {
13191
13481
  __name(registerConnectionTools, "registerConnectionTools");
13192
13482
 
13193
13483
  // src/tools/actors.ts
13194
- import { z as z53 } from "zod";
13484
+ import { z as z54 } from "zod";
13195
13485
  function registerActorTools(server, api) {
13196
- server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. Usa el `id` como `actorId` al crear o editar una tarea. Solo aparecen los personajes listos para usar; si la lista est\xE1 vac\xEDa, dile al usuario que cree uno en productmaker.app antes de intentar asignarlo. No modifica nada.", z53.object({}).shape, async () => {
13486
+ server.tool("list_actors", "Lista los personajes guardados del usuario (personas creadas con IA que pueden protagonizar los videos e im\xE1genes). Devuelve id, nombre, descripci\xF3n, g\xE9nero y voz. Usa el `id` como `actorId` al crear o editar una tarea. Solo aparecen los personajes listos para usar; si la lista est\xE1 vac\xEDa, dile al usuario que cree uno en productmaker.app antes de intentar asignarlo. No modifica nada.", z54.object({}).shape, READS_INTERNAL, async () => {
13197
13487
  try {
13198
13488
  const rows = await api.getJson("/v1/actors");
13199
13489
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -13214,116 +13504,145 @@ function registerActorTools(server, api) {
13214
13504
  __name(registerActorTools, "registerActorTools");
13215
13505
 
13216
13506
  // src/tools/publish.ts
13217
- import { z as z54 } from "zod";
13218
- var ShopifyInput = z54.object({
13219
- taskId: z54.string().min(1),
13220
- shopDomain: z54.string().min(1),
13507
+ import { z as z55 } from "zod";
13508
+ var ShopifyInput = z55.object({
13509
+ taskId: z55.string().min(1),
13510
+ shopDomain: z55.string().min(1),
13221
13511
  /** Override: number of landing images to include. Default = ALL images the
13222
13512
  * task generated (the MCP fetches /v1/results first to count). Bounded
13223
13513
  * generously here; the backend validates each index against
13224
13514
  * landing.images.length and rejects out-of-range. */
13225
- numLandingImages: z54.number().int().min(1).max(20).optional(),
13226
- variantIndex: z54.number().int().min(0).optional(),
13515
+ numLandingImages: z55.number().int().min(1).max(20).optional(),
13516
+ variantIndex: z55.number().int().min(0).optional(),
13227
13517
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
13228
13518
  * When true, the landing renders variant-picker pills; when false, it ships
13229
13519
  * a single-option product page. Defaults to false. */
13230
- showVariantPicker: z54.boolean().optional(),
13520
+ showVariantPicker: z55.boolean().optional(),
13231
13521
  /** Default `auto` updates the product previously published for this task and
13232
13522
  * shop. Use `existing` with targetProductId to choose another product, or
13233
13523
  * `new` only when the user explicitly wants a duplicate/new listing. */
13234
- publicationMode: z54.enum([
13524
+ publicationMode: z55.enum([
13235
13525
  "auto",
13236
13526
  "existing",
13237
13527
  "new"
13238
13528
  ]).optional(),
13239
13529
  /** Shopify product GID, numeric id, or admin product URL. Required when
13240
13530
  * publicationMode is `existing`. */
13241
- targetProductId: z54.string().trim().min(1).max(300).optional()
13531
+ targetProductId: z55.string().trim().min(1).max(300).optional(),
13532
+ /** Add an announcement bar text above add-to-cart (optional). */
13533
+ announcementBarEnabled: z55.boolean().optional(),
13534
+ announcementBarText: z55.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
13242
13535
  });
13243
- var McpCampaignConfig = z54.object({
13536
+ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
13537
+ if (value.announcementBarEnabled && !value.announcementBarText) {
13538
+ ctx.addIssue({
13539
+ code: z55.ZodIssueCode.custom,
13540
+ path: [
13541
+ "announcementBarText"
13542
+ ],
13543
+ message: "announcementBarText is required when announcementBarEnabled is true"
13544
+ });
13545
+ }
13546
+ if (!value.announcementBarEnabled && value.announcementBarText) {
13547
+ ctx.addIssue({
13548
+ code: z55.ZodIssueCode.custom,
13549
+ path: [
13550
+ "announcementBarText"
13551
+ ],
13552
+ message: "announcementBarText requires announcementBarEnabled to be true"
13553
+ });
13554
+ }
13555
+ });
13556
+ function parseShopifyInput(rawInput) {
13557
+ const parsed = ShopifyInputWithAnnouncementBarValidation.safeParse(rawInput);
13558
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
13559
+ return parsed.data;
13560
+ }
13561
+ __name(parseShopifyInput, "parseShopifyInput");
13562
+ var McpCampaignConfig = z55.object({
13244
13563
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
13245
13564
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
13246
13565
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
13247
- dailyBudgetUsd: z54.number().positive().optional(),
13566
+ dailyBudgetUsd: z55.number().positive().optional(),
13248
13567
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
13249
13568
  * ad set). TikTok takes only the first entry. */
13250
- targetCountries: z54.array(z54.string().length(2)).max(1).optional(),
13251
- campaignName: z54.string().trim().min(1).max(100).optional()
13569
+ targetCountries: z55.array(z55.string().length(2)).max(1).optional(),
13570
+ campaignName: z55.string().trim().min(1).max(100).optional()
13252
13571
  });
13253
13572
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
13254
- var MetaInput = z54.object({
13255
- taskId: z54.string().min(1),
13573
+ var MetaInput = z55.object({
13574
+ taskId: z55.string().min(1),
13256
13575
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
13257
- portfolioId: z54.string().trim().min(1),
13576
+ portfolioId: z55.string().trim().min(1),
13258
13577
  /** Exact Facebook Page selected from `list_meta_destinations`. */
13259
- pageId: z54.string().trim().min(1),
13578
+ pageId: z55.string().trim().min(1),
13260
13579
  adAccountId: McpAdAccountId,
13261
- pixelId: z54.string().min(1).optional(),
13262
- landingUrl: z54.string().url().optional(),
13580
+ pixelId: z55.string().min(1).optional(),
13581
+ landingUrl: z55.string().url().optional(),
13263
13582
  /** Select which angles to publish by 0-based index. Omit to publish all
13264
13583
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
13265
- angleIndexes: z54.array(z54.number().int().nonnegative()).optional(),
13584
+ angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
13266
13585
  campaignConfig: McpCampaignConfig.optional(),
13267
13586
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
13268
13587
  * uses the auto-generated copy attached to the task. Capped at 500 chars
13269
13588
  * (Meta's primary-text limit). */
13270
- primaryText: z54.string().trim().min(1).max(500).optional(),
13589
+ primaryText: z55.string().trim().min(1).max(500).optional(),
13271
13590
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
13272
13591
  * (wa.me) instead of a landing page. */
13273
- phoneNumber: z54.string().min(8).max(32).optional()
13592
+ phoneNumber: z55.string().min(8).max(32).optional()
13274
13593
  });
13275
- var GroupedTaskSource = z54.object({
13276
- type: z54.literal("task"),
13277
- taskId: z54.string().min(1),
13278
- angleIndex: z54.number().int().min(0).optional(),
13279
- videoVariant: z54.enum([
13594
+ var GroupedTaskSource = z55.object({
13595
+ type: z55.literal("task"),
13596
+ taskId: z55.string().min(1),
13597
+ angleIndex: z55.number().int().min(0).optional(),
13598
+ videoVariant: z55.enum([
13280
13599
  "with_subtitles",
13281
13600
  "without_subtitles"
13282
13601
  ]).optional(),
13283
- imageVariantIndex: z54.number().int().min(0).optional()
13284
- });
13285
- var GroupedMediaSource = z54.object({
13286
- type: z54.literal("media"),
13287
- url: z54.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
13288
- path: z54.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
13289
- thumbnailUrl: z54.string().url().optional(),
13290
- thumbnailPath: z54.string().min(1).optional()
13602
+ imageVariantIndex: z55.number().int().min(0).optional()
13603
+ });
13604
+ var GroupedMediaSource = z55.object({
13605
+ type: z55.literal("media"),
13606
+ url: z55.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
13607
+ path: z55.string().min(1).optional().describe('Local file path only when this MCP runs over stdio. On the hosted MCP, call request_media_upload and then use source.type="asset".'),
13608
+ thumbnailUrl: z55.string().url().optional(),
13609
+ thumbnailPath: z55.string().min(1).optional()
13291
13610
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
13292
13611
  message: "media source needs exactly one of `url` or `path`"
13293
13612
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
13294
13613
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
13295
13614
  });
13296
- var GroupedUploadedAssetSource = z54.object({
13297
- type: z54.literal("asset"),
13615
+ var GroupedUploadedAssetSource = z55.object({
13616
+ type: z55.literal("asset"),
13298
13617
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
13299
- assetId: z54.string().uuid(),
13300
- thumbnailAssetId: z54.string().uuid().optional()
13618
+ assetId: z55.string().uuid(),
13619
+ thumbnailAssetId: z55.string().uuid().optional()
13301
13620
  });
13302
- var GroupedCreative = z54.object({
13303
- type: z54.enum([
13621
+ var GroupedCreative = z55.object({
13622
+ type: z55.enum([
13304
13623
  "image",
13305
13624
  "video"
13306
13625
  ]),
13307
- source: z54.union([
13626
+ source: z55.union([
13308
13627
  GroupedTaskSource,
13309
13628
  GroupedMediaSource,
13310
13629
  GroupedUploadedAssetSource
13311
13630
  ]),
13312
- title: z54.string().max(120).optional(),
13313
- description: z54.string().max(500).optional(),
13631
+ title: z55.string().max(120).optional(),
13632
+ description: z55.string().max(500).optional(),
13314
13633
  cta: MetaCtaType.optional(),
13315
- creativeId: z54.string().min(1).optional()
13634
+ creativeId: z55.string().min(1).optional()
13316
13635
  });
13317
- var GroupedCampaignConfigBase = z54.object({
13636
+ var GroupedCampaignConfigBase = z55.object({
13318
13637
  campaignType: MetaCampaignType.default("sales"),
13319
13638
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
13320
13639
  budgetMode: MetaBudgetMode.optional(),
13321
- campaignDailyBudget: z54.number().int().positive().optional(),
13322
- adSets: z54.array(z54.object({
13323
- tempId: z54.string().min(1).max(80),
13324
- name: z54.string().min(1).max(100),
13325
- dailyBudget: z54.number().int().positive().optional(),
13326
- creativeIds: z54.array(z54.string().min(1)).min(1)
13640
+ campaignDailyBudget: z55.number().int().positive().optional(),
13641
+ adSets: z55.array(z55.object({
13642
+ tempId: z55.string().min(1).max(80),
13643
+ name: z55.string().min(1).max(100),
13644
+ dailyBudget: z55.number().int().positive().optional(),
13645
+ creativeIds: z55.array(z55.string().min(1)).min(1)
13327
13646
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
13328
13647
  /**
13329
13648
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -13331,17 +13650,17 @@ var GroupedCampaignConfigBase = z54.object({
13331
13650
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
13332
13651
  * the API since #849 — unreachable from this tool.
13333
13652
  */
13334
- destination: z54.discriminatedUnion("type", [
13335
- z54.object({
13336
- type: z54.literal("landing"),
13337
- landingUrl: z54.string().url()
13653
+ destination: z55.discriminatedUnion("type", [
13654
+ z55.object({
13655
+ type: z55.literal("landing"),
13656
+ landingUrl: z55.string().url()
13338
13657
  }),
13339
- z54.object({
13340
- type: z54.literal("whatsapp"),
13341
- phoneNumber: z54.string().min(8).max(32)
13658
+ z55.object({
13659
+ type: z55.literal("whatsapp"),
13660
+ phoneNumber: z55.string().min(8).max(32)
13342
13661
  })
13343
13662
  ]),
13344
- saveAsDefault: z54.boolean().default(false),
13663
+ saveAsDefault: z55.boolean().default(false),
13345
13664
  budgetPolicy: MetaBudgetPolicy.optional()
13346
13665
  });
13347
13666
  function resolveBudgetMode(value) {
@@ -13355,7 +13674,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
13355
13674
  const budgetMode = resolveBudgetMode(value);
13356
13675
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
13357
13676
  ctx.addIssue({
13358
- code: z54.ZodIssueCode.custom,
13677
+ code: z55.ZodIssueCode.custom,
13359
13678
  path: [
13360
13679
  "campaignDailyBudget"
13361
13680
  ],
@@ -13366,7 +13685,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
13366
13685
  value.adSets.forEach((adSet, index) => {
13367
13686
  if (adSet.dailyBudget === void 0) {
13368
13687
  ctx.addIssue({
13369
- code: z54.ZodIssueCode.custom,
13688
+ code: z55.ZodIssueCode.custom,
13370
13689
  path: [
13371
13690
  "adSets",
13372
13691
  index,
@@ -13381,26 +13700,26 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
13381
13700
  ...value,
13382
13701
  budgetMode: resolveBudgetMode(value)
13383
13702
  }));
13384
- var PublishCreativesToMetaInputBase = z54.object({
13385
- creatives: z54.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
13703
+ var PublishCreativesToMetaInputBase = z55.object({
13704
+ creatives: z55.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
13386
13705
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
13387
- portfolioId: z54.string().trim().min(1),
13706
+ portfolioId: z55.string().trim().min(1),
13388
13707
  /** Exact Facebook Page selected from `list_meta_destinations`. */
13389
- pageId: z54.string().trim().min(1),
13708
+ pageId: z55.string().trim().min(1),
13390
13709
  adAccountId: McpAdAccountId,
13391
- pixelId: z54.string().min(1).optional(),
13710
+ pixelId: z55.string().min(1).optional(),
13392
13711
  /** Required unless `campaignConfig.destination` is whatsapp — see the
13393
13712
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
13394
- landingUrl: z54.string().url().optional(),
13395
- targetCountry: z54.string().length(2),
13396
- campaignName: z54.string().trim().min(1).max(100),
13713
+ landingUrl: z55.string().url().optional(),
13714
+ targetCountry: z55.string().length(2),
13715
+ campaignName: z55.string().trim().min(1).max(100),
13397
13716
  campaignConfig: GroupedCampaignConfig.optional()
13398
13717
  });
13399
13718
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
13400
13719
  const destination = value.campaignConfig?.destination;
13401
13720
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
13402
13721
  ctx.addIssue({
13403
- code: z54.ZodIssueCode.custom,
13722
+ code: z55.ZodIssueCode.custom,
13404
13723
  path: [
13405
13724
  "landingUrl"
13406
13725
  ],
@@ -13409,7 +13728,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
13409
13728
  }
13410
13729
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
13411
13730
  ctx.addIssue({
13412
- code: z54.ZodIssueCode.custom,
13731
+ code: z55.ZodIssueCode.custom,
13413
13732
  path: [
13414
13733
  "campaignConfig",
13415
13734
  "destination",
@@ -13428,16 +13747,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
13428
13747
  }
13429
13748
  return value;
13430
13749
  });
13431
- var PresignUploadResponseSchema = z54.object({
13432
- uploadUrl: z54.string().url().refine((value) => {
13750
+ var PresignUploadResponseSchema = z55.object({
13751
+ uploadUrl: z55.string().url().refine((value) => {
13433
13752
  const url = new URL(value);
13434
13753
  return !url.username && !url.password && url.protocol === "https:";
13435
13754
  }, "presigned upload URL must be HTTPS without embedded credentials"),
13436
- publicUrl: z54.string().url().refine((value) => {
13755
+ publicUrl: z55.string().url().refine((value) => {
13437
13756
  const url = new URL(value);
13438
13757
  return !url.username && !url.password;
13439
13758
  }, "public media URL must not contain embedded credentials"),
13440
- stagingAssetId: z54.string().uuid()
13759
+ stagingAssetId: z55.string().uuid()
13441
13760
  });
13442
13761
  async function uploadResolvedMedia(api, media, assetRole) {
13443
13762
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -13462,22 +13781,23 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
13462
13781
  return uploadResolvedMedia(api, media, assetRole);
13463
13782
  }
13464
13783
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
13465
- var TikTokInput = z54.object({
13466
- taskId: z54.string().min(1),
13467
- advertiserId: z54.string().min(1),
13468
- pixelId: z54.string().min(1).optional(),
13469
- landingUrl: z54.string().url().optional(),
13470
- angleIndexes: z54.array(z54.number().int().nonnegative()).optional(),
13784
+ var TikTokInput = z55.object({
13785
+ taskId: z55.string().min(1),
13786
+ advertiserId: z55.string().min(1),
13787
+ pixelId: z55.string().min(1).optional(),
13788
+ landingUrl: z55.string().url().optional(),
13789
+ angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
13471
13790
  campaignConfig: McpCampaignConfig.optional(),
13472
13791
  /** Override the ad's description (TikTok's body copy). If omitted,
13473
13792
  * ProductMaker uses the auto-generated copy attached to the task. */
13474
- primaryText: z54.string().trim().min(1).max(500).optional()
13793
+ primaryText: z55.string().trim().min(1).max(500).optional()
13475
13794
  });
13476
13795
  function registerPublishTools(server, api, opts = {}) {
13477
13796
  const transport = opts.transport ?? "http";
13478
- server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
13797
+ server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input) => {
13479
13798
  try {
13480
- if (input.publicationMode === "existing" && !input.targetProductId) {
13799
+ const parsed = parseShopifyInput(input);
13800
+ if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
13481
13801
  return {
13482
13802
  isError: true,
13483
13803
  content: [
@@ -13488,29 +13808,31 @@ function registerPublishTools(server, api, opts = {}) {
13488
13808
  ]
13489
13809
  };
13490
13810
  }
13491
- const variantIndex = input.variantIndex ?? 0;
13492
- const availableImages = await fetchLandingImageCount(api, input.taskId);
13811
+ const variantIndex = parsed.variantIndex ?? 0;
13812
+ const availableImages = await fetchLandingImageCount(api, parsed.taskId);
13493
13813
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
13494
- const numImages = Math.min(input.numLandingImages ?? effectiveAvailable, effectiveAvailable);
13814
+ const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
13495
13815
  const selectedLandingImages = Array.from({
13496
13816
  length: numImages
13497
13817
  }, (_, i) => ({
13498
13818
  imageIndex: i,
13499
13819
  variantIndex
13500
13820
  }));
13501
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/publish/shopify`, {
13502
- shopDomain: input.shopDomain,
13821
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(parsed.taskId)}/publish/shopify`, {
13822
+ shopDomain: parsed.shopDomain,
13503
13823
  selectedLandingImages,
13504
- showVariantPicker: input.showVariantPicker,
13505
- publicationMode: input.publicationMode,
13506
- targetProductId: input.targetProductId
13824
+ showVariantPicker: parsed.showVariantPicker,
13825
+ publicationMode: parsed.publicationMode,
13826
+ targetProductId: parsed.targetProductId,
13827
+ announcementBarEnabled: parsed.announcementBarEnabled,
13828
+ announcementBarText: parsed.announcementBarText
13507
13829
  });
13508
13830
  return renderShopifyPublishMarkdown(r);
13509
13831
  } catch (e) {
13510
13832
  throw translateError(e);
13511
13833
  }
13512
13834
  });
13513
- server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
13835
+ server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
13514
13836
  try {
13515
13837
  const body = {
13516
13838
  ...rest
@@ -13538,7 +13860,7 @@ function registerPublishTools(server, api, opts = {}) {
13538
13860
  throw translateError(e);
13539
13861
  }
13540
13862
  });
13541
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
13863
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput) => {
13542
13864
  try {
13543
13865
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
13544
13866
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -13628,7 +13950,7 @@ function registerPublishTools(server, api, opts = {}) {
13628
13950
  throw translateError(e);
13629
13951
  }
13630
13952
  });
13631
- server.tool("publish_to_tiktok", "Crea una campa\xF1a de TikTok Ads desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
13953
+ server.tool("publish_to_tiktok", "Crea una campa\xF1a de TikTok Ads desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en TikTok Ads Manager. Use `list_tiktok_advertisers` antes para un advertiserId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 ej. [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 nunca el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, primero se usa), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_tiktok_campaign_status` para chequear el estado.", TikTokInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
13632
13954
  try {
13633
13955
  const body = {
13634
13956
  ...rest
@@ -13718,38 +14040,38 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
13718
14040
 
13719
14041
  // src/tools/meta-upload.ts
13720
14042
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
13721
- import { z as z55 } from "zod";
13722
- var UploadSessionInputBase = z55.object({
13723
- mediaKind: z55.enum([
14043
+ import { z as z56 } from "zod";
14044
+ var UploadSessionInputBase = z56.object({
14045
+ mediaKind: z56.enum([
13724
14046
  "image",
13725
14047
  "video",
13726
14048
  "mixed"
13727
14049
  ]).default("mixed").describe("The kind of file the user will select. Use image for product references and landing pages, video for a single video, and mixed only when the consumer supports both."),
13728
- assetRole: z55.enum([
14050
+ assetRole: z56.enum([
13729
14051
  "media",
13730
14052
  "thumbnail"
13731
14053
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
13732
- uploadPurpose: z55.enum([
14054
+ uploadPurpose: z56.enum([
13733
14055
  "product_reference",
13734
14056
  "creative",
13735
14057
  "generic"
13736
14058
  ]).default("generic").describe("The user's purpose for these files. Use product_reference for a product photo used by tasks or landings, creative for media that will become an ad or creative, and generic only when neither applies."),
13737
- maxFiles: z55.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
14059
+ maxFiles: z56.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
13738
14060
  });
13739
- var OpenAiFileReference = z55.object({
13740
- download_url: z55.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
13741
- file_id: z55.string().min(1),
13742
- mime_type: z55.string().min(1).optional(),
13743
- file_name: z55.string().min(1).optional()
14061
+ var OpenAiFileReference = z56.object({
14062
+ download_url: z56.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
14063
+ file_id: z56.string().min(1),
14064
+ mime_type: z56.string().min(1).optional(),
14065
+ file_name: z56.string().min(1).optional()
13744
14066
  });
13745
14067
  var UploadSessionToolInputShape = {
13746
14068
  ...UploadSessionInputBase.shape,
13747
- files: z55.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
14069
+ files: z56.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
13748
14070
  };
13749
- var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
14071
+ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
13750
14072
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
13751
14073
  ctx.addIssue({
13752
- code: z55.ZodIssueCode.custom,
14074
+ code: z56.ZodIssueCode.custom,
13753
14075
  path: [
13754
14076
  "mediaKind"
13755
14077
  ],
@@ -13758,7 +14080,7 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
13758
14080
  }
13759
14081
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
13760
14082
  ctx.addIssue({
13761
- code: z55.ZodIssueCode.custom,
14083
+ code: z56.ZodIssueCode.custom,
13762
14084
  path: [
13763
14085
  "maxFiles"
13764
14086
  ],
@@ -13767,7 +14089,7 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
13767
14089
  }
13768
14090
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
13769
14091
  ctx.addIssue({
13770
- code: z55.ZodIssueCode.custom,
14092
+ code: z56.ZodIssueCode.custom,
13771
14093
  path: [
13772
14094
  "maxFiles"
13773
14095
  ],
@@ -13775,38 +14097,38 @@ var UploadSessionToolInput = z55.object(UploadSessionToolInputShape).superRefine
13775
14097
  });
13776
14098
  }
13777
14099
  });
13778
- var UploadSessionResponse = z55.object({
13779
- sessionId: z55.string().uuid(),
13780
- uploadPageUrl: z55.string().url(),
13781
- mediaKind: z55.enum([
14100
+ var UploadSessionResponse = z56.object({
14101
+ sessionId: z56.string().uuid(),
14102
+ uploadPageUrl: z56.string().url(),
14103
+ mediaKind: z56.enum([
13782
14104
  "image",
13783
14105
  "video",
13784
14106
  "mixed"
13785
14107
  ]),
13786
- assetRole: z55.enum([
14108
+ assetRole: z56.enum([
13787
14109
  "media",
13788
14110
  "thumbnail"
13789
14111
  ]),
13790
- maxFiles: z55.number().int().positive(),
13791
- expiresAt: z55.string().datetime()
14112
+ maxFiles: z56.number().int().positive(),
14113
+ expiresAt: z56.string().datetime()
13792
14114
  });
13793
- var UploadStatusInput = z55.object({
13794
- sessionId: z55.string().uuid().describe("The sessionId returned by request_media_upload.")
14115
+ var UploadStatusInput = z56.object({
14116
+ sessionId: z56.string().uuid().describe("The sessionId returned by request_media_upload.")
13795
14117
  });
13796
- var UniversalUploadStatusResponse = z55.discriminatedUnion("status", [
13797
- z55.object({
13798
- status: z55.literal("pending"),
13799
- assets: z55.array(z55.never()).length(0)
14118
+ var UniversalUploadStatusResponse = z56.discriminatedUnion("status", [
14119
+ z56.object({
14120
+ status: z56.literal("pending"),
14121
+ assets: z56.array(z56.never()).length(0)
13800
14122
  }),
13801
- z55.object({
13802
- status: z55.literal("uploaded"),
13803
- assets: z55.array(z55.object({
13804
- assetId: z55.string().uuid(),
13805
- mediaKind: z55.enum([
14123
+ z56.object({
14124
+ status: z56.literal("uploaded"),
14125
+ assets: z56.array(z56.object({
14126
+ assetId: z56.string().uuid(),
14127
+ mediaKind: z56.enum([
13806
14128
  "image",
13807
14129
  "video"
13808
14130
  ]),
13809
- assetRole: z55.enum([
14131
+ assetRole: z56.enum([
13810
14132
  "media",
13811
14133
  "thumbnail"
13812
14134
  ])
@@ -13814,13 +14136,13 @@ var UniversalUploadStatusResponse = z55.discriminatedUnion("status", [
13814
14136
  })
13815
14137
  ]);
13816
14138
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
13817
- uploadPurpose: z55.enum([
14139
+ uploadPurpose: z56.enum([
13818
14140
  "product_reference",
13819
14141
  "creative",
13820
14142
  "generic"
13821
14143
  ]),
13822
- checkToolName: z55.literal("check_media_upload"),
13823
- resolveEndpoint: z55.string().url()
14144
+ checkToolName: z56.literal("check_media_upload"),
14145
+ resolveEndpoint: z56.string().url()
13824
14146
  });
13825
14147
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
13826
14148
  const url = new URL(uploadPageUrl);
@@ -13854,6 +14176,7 @@ function registerMediaUploadTools(server, api) {
13854
14176
  title: "Subir archivos",
13855
14177
  description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
13856
14178
  inputSchema: UploadSessionToolInputShape,
14179
+ annotations: CREATES_INTERNAL,
13857
14180
  outputSchema: UniversalUploadStructuredResponse.shape,
13858
14181
  _meta: {
13859
14182
  ui: {
@@ -13894,6 +14217,7 @@ function registerMediaUploadTools(server, api) {
13894
14217
  server.registerTool("check_media_upload", {
13895
14218
  description: "Comprueba la carga universal. Ll\xE1mala cuando el usuario diga que ya subi\xF3 sus archivos: es la \xFAnica forma de enterarte, porque el App no manda mensajes al chat. Si status=pending, el usuario todav\xEDa no ha terminado: d\xEDselo y espera, no inventes un assetId ni abras otra sesi\xF3n de carga. Si status=uploaded, conserva assets[] para la petici\xF3n anterior y pasa cada assetId \xFAnicamente a un consumidor compatible. Un asset de video solo sirve donde se admite video; las referencias de producto, imagen y landing requieren mediaKind=image. No muestres IDs t\xE9cnicos como JSON al usuario.",
13896
14219
  inputSchema: UploadStatusInput.shape,
14220
+ annotations: READS_INTERNAL,
13897
14221
  _meta: {
13898
14222
  "openai/widgetAccessible": true
13899
14223
  }
@@ -13909,16 +14233,16 @@ function registerMediaUploadTools(server, api) {
13909
14233
  __name(registerMediaUploadTools, "registerMediaUploadTools");
13910
14234
 
13911
14235
  // src/tools/campaigns.ts
13912
- import { z as z56 } from "zod";
13913
- var MetaStatusInput = z56.object({
13914
- campaignId: z56.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
14236
+ import { z as z57 } from "zod";
14237
+ var MetaStatusInput = z57.object({
14238
+ campaignId: z57.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
13915
14239
  });
13916
- var TikTokStatusInput = z56.object({
13917
- campaignId: z56.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
13918
- advertiserId: z56.string().min(1).describe("The same advertiserId used to publish the campaign.")
14240
+ var TikTokStatusInput = z57.object({
14241
+ campaignId: z57.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
14242
+ advertiserId: z57.string().min(1).describe("The same advertiserId used to publish the campaign.")
13919
14243
  });
13920
14244
  function registerCampaignTools(server, api) {
13921
- server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, async (input) => {
14245
+ server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, READS_EXTERNAL, async (input) => {
13922
14246
  try {
13923
14247
  const r = await api.getJson(`/v1/facebook/campaigns/${encodeURIComponent(input.campaignId)}`);
13924
14248
  return renderMetaStatusMarkdown(r);
@@ -13926,7 +14250,7 @@ function registerCampaignTools(server, api) {
13926
14250
  throw translateError(e);
13927
14251
  }
13928
14252
  });
13929
- server.tool("get_tiktok_campaign_status", "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada. Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, async (input) => {
14253
+ server.tool("get_tiktok_campaign_status", "Consulta el estado actual + reporte lifetime (spend, impressions, clicks, reach) de una campa\xF1a TikTok publicada. Requiere `advertiserId` (el mismo con el que se cre\xF3). Llama esto DESPU\xC9S de `publish_to_tiktok` para confirmar creaci\xF3n correcta y reportar performance. Es read-only.", TikTokStatusInput.shape, READS_EXTERNAL, async (input) => {
13930
14254
  try {
13931
14255
  const r = await api.getJson(`/v1/tiktok/campaigns/${encodeURIComponent(input.campaignId)}?advertiserId=${encodeURIComponent(input.advertiserId)}`);
13932
14256
  return renderTikTokStatusMarkdown(r);
@@ -14012,10 +14336,10 @@ function renderTikTokStatusMarkdown(r) {
14012
14336
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
14013
14337
 
14014
14338
  // ../../packages/product-economics/src/product-costing.ts
14015
- import { z as z58 } from "zod";
14339
+ import { z as z59 } from "zod";
14016
14340
 
14017
14341
  // ../../packages/product-economics/src/product-costing-countries.ts
14018
- import { z as z57 } from "zod";
14342
+ import { z as z58 } from "zod";
14019
14343
 
14020
14344
  // ../../packages/product-economics/src/product-costing-shipping-references.json
14021
14345
  var product_costing_shipping_references_default = {
@@ -14079,21 +14403,21 @@ var product_costing_shipping_references_default = {
14079
14403
  };
14080
14404
 
14081
14405
  // ../../packages/product-economics/src/product-costing-countries.ts
14082
- var ShippingReferenceSchema = z57.object({
14083
- reference: z57.number().positive(),
14084
- min: z57.number().nonnegative(),
14085
- max: z57.number().positive(),
14086
- updatedAt: z57.string().date(),
14087
- confidence: z57.enum([
14406
+ var ShippingReferenceSchema = z58.object({
14407
+ reference: z58.number().positive(),
14408
+ min: z58.number().nonnegative(),
14409
+ max: z58.number().positive(),
14410
+ updatedAt: z58.string().date(),
14411
+ confidence: z58.enum([
14088
14412
  "high",
14089
14413
  "medium",
14090
14414
  "medium-low"
14091
14415
  ]),
14092
- sources: z57.array(z57.string().url().startsWith("https://")).min(1).max(3)
14416
+ sources: z58.array(z58.string().url().startsWith("https://")).min(1).max(3)
14093
14417
  }).superRefine(({ reference, min, max }, context) => {
14094
14418
  if (min > reference || reference > max) {
14095
14419
  context.addIssue({
14096
- code: z57.ZodIssueCode.custom,
14420
+ code: z58.ZodIssueCode.custom,
14097
14421
  message: "shipping_reference_outside_range",
14098
14422
  path: [
14099
14423
  "reference"
@@ -14101,7 +14425,7 @@ var ShippingReferenceSchema = z57.object({
14101
14425
  });
14102
14426
  }
14103
14427
  });
14104
- var SHIPPING_REFERENCES = z57.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
14428
+ var SHIPPING_REFERENCES = z58.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
14105
14429
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
14106
14430
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
14107
14431
  "MX",
@@ -14283,18 +14607,18 @@ function getProductCostingCountryName(country) {
14283
14607
  __name(getProductCostingCountryName, "getProductCostingCountryName");
14284
14608
 
14285
14609
  // ../../packages/product-economics/src/product-costing.ts
14286
- var ProductCostingInputSchema = z58.object({
14287
- country: z58.string().refine(isProductCostingCountryCode, "unsupported_country"),
14288
- supplierCost: z58.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
14289
- shippingCost: z58.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
14290
- returnRate: z58.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
14291
- cancellationRate: z58.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
14292
- adsManagerCpa: z58.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
14293
- desiredMargin: z58.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
14610
+ var ProductCostingInputSchema = z59.object({
14611
+ country: z59.string().refine(isProductCostingCountryCode, "unsupported_country"),
14612
+ supplierCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
14613
+ shippingCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
14614
+ returnRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
14615
+ cancellationRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
14616
+ adsManagerCpa: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
14617
+ desiredMargin: z59.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
14294
14618
  }).superRefine(({ returnRate, cancellationRate }, context) => {
14295
14619
  if (returnRate + cancellationRate >= 1) {
14296
14620
  context.addIssue({
14297
- code: z58.ZodIssueCode.custom,
14621
+ code: z59.ZodIssueCode.custom,
14298
14622
  message: "delivery_rate_positive",
14299
14623
  path: [
14300
14624
  "cancellationRate"
@@ -14361,14 +14685,14 @@ function calculateProductCosting(rawInput) {
14361
14685
  __name(calculateProductCosting, "calculateProductCosting");
14362
14686
 
14363
14687
  // src/tools/product-costing.ts
14364
- import { z as z59 } from "zod";
14365
- var optionalAmount = /* @__PURE__ */ __name((description) => z59.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
14366
- var optionalPercent = /* @__PURE__ */ __name((description) => z59.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
14367
- var ProductCostingToolInput = z59.object({
14368
- country: z59.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
14688
+ import { z as z60 } from "zod";
14689
+ var optionalAmount = /* @__PURE__ */ __name((description) => z60.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
14690
+ var optionalPercent = /* @__PURE__ */ __name((description) => z60.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
14691
+ var ProductCostingToolInput = z60.object({
14692
+ country: z60.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
14369
14693
  message: "Pa\xEDs no soportado por ProductMaker."
14370
14694
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
14371
- supplierCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
14695
+ supplierCost: z60.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
14372
14696
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
14373
14697
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
14374
14698
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -14379,7 +14703,7 @@ var ProductCostingToolInput = z59.object({
14379
14703
  desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
14380
14704
  });
14381
14705
  function registerProductCostingTools(server) {
14382
- server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
14706
+ server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
14383
14707
  if (input.country !== void 0 && input.country !== "CO" && [
14384
14708
  input.supplierCostCop,
14385
14709
  input.shippingCostCop,
@@ -14500,7 +14824,7 @@ function formatPercent(value) {
14500
14824
  __name(formatPercent, "formatPercent");
14501
14825
 
14502
14826
  // src/tools/product-research.ts
14503
- import { z as z60 } from "zod";
14827
+ import { z as z61 } from "zod";
14504
14828
 
14505
14829
  // src/tools/product-research-idempotency.ts
14506
14830
  import { createHash as createHash2 } from "crypto";
@@ -14616,17 +14940,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
14616
14940
  ]);
14617
14941
  var POLL_INTERVAL_MS = 5e3;
14618
14942
  var MAX_WAIT_SECONDS = 120;
14619
- var FindInput = z60.object({
14943
+ var FindInput = z61.object({
14620
14944
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
14621
14945
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
14622
- confirmed: z60.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
14623
- waitSeconds: z60.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
14946
+ confirmed: z61.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
14947
+ waitSeconds: z61.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
14624
14948
  });
14625
- var GetInput = z60.object({
14626
- runId: z60.string().uuid().describe("ID del run devuelto por find_winning_products.")
14949
+ var GetInput = z61.object({
14950
+ runId: z61.string().uuid().describe("ID del run devuelto por find_winning_products.")
14627
14951
  });
14628
- var ListInput = z60.object({
14629
- limit: z60.number().int().min(1).max(20).default(10)
14952
+ var ListInput = z61.object({
14953
+ limit: z61.number().int().min(1).max(20).default(10)
14630
14954
  });
14631
14955
  function textResult(text) {
14632
14956
  return {
@@ -14651,7 +14975,7 @@ async function pollUntilTerminal(api, runId, waitSeconds) {
14651
14975
  }
14652
14976
  __name(pollUntilTerminal, "pollUntilTerminal");
14653
14977
  function registerProductResearchTools(server, api) {
14654
- server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, async (input) => {
14978
+ server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, CREATES_EXTERNAL, async (input) => {
14655
14979
  const parsed = FindInput.parse(input);
14656
14980
  if (!parsed.confirmed) {
14657
14981
  throw mcp(McpErrorCode.INVALID_INPUT, `This research run costs ${PRICE_CREDITS} credits. Tell the user the cost, get an explicit yes, then call again with confirmed: true.`);
@@ -14685,7 +15009,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
14685
15009
  throw translateError(error);
14686
15010
  }
14687
15011
  });
14688
- server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, async (input) => {
15012
+ server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, READS_INTERNAL, async (input) => {
14689
15013
  const parsed = GetInput.parse(input);
14690
15014
  try {
14691
15015
  const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
@@ -14705,7 +15029,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
14705
15029
  throw translateError(error);
14706
15030
  }
14707
15031
  });
14708
- server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, async (input) => {
15032
+ server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, READS_INTERNAL, async (input) => {
14709
15033
  const parsed = ListInput.parse(input);
14710
15034
  try {
14711
15035
  const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
@@ -14718,14 +15042,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
14718
15042
  __name(registerProductResearchTools, "registerProductResearchTools");
14719
15043
 
14720
15044
  // src/tools/dropshipping-projects.ts
14721
- import { z as z61 } from "zod";
14722
- var ProjectId = z61.string().uuid();
14723
- var DropshippingProjectStatus = z61.enum([
15045
+ import { z as z62 } from "zod";
15046
+ var ProjectId = z62.string().uuid();
15047
+ var DropshippingProjectStatus = z62.enum([
14724
15048
  "active",
14725
15049
  "completed",
14726
15050
  "archived"
14727
15051
  ]);
14728
- var DropshippingProjectPhase = z61.enum([
15052
+ var DropshippingProjectPhase = z62.enum([
14729
15053
  "research",
14730
15054
  "costing",
14731
15055
  "media",
@@ -14734,21 +15058,21 @@ var DropshippingProjectPhase = z61.enum([
14734
15058
  "advertising",
14735
15059
  "complete"
14736
15060
  ]);
14737
- var DropshippingRewardMilestone = z61.enum([
15061
+ var DropshippingRewardMilestone = z62.enum([
14738
15062
  "product_selected",
14739
15063
  "costing_saved",
14740
15064
  "product_media_ready",
14741
15065
  "creatives_completed",
14742
15066
  "campaign_created"
14743
15067
  ]);
14744
- var TargetCountry = z61.enum([
15068
+ var TargetCountry = z62.enum([
14745
15069
  "CO",
14746
15070
  "MX",
14747
15071
  "PE",
14748
15072
  "EC",
14749
15073
  "CL"
14750
15074
  ]);
14751
- var ResearchCategory = z61.enum([
15075
+ var ResearchCategory = z62.enum([
14752
15076
  "auto",
14753
15077
  "automotive",
14754
15078
  "kitchen",
@@ -14763,7 +15087,7 @@ var ResearchCategory = z61.enum([
14763
15087
  "garden",
14764
15088
  "cleaning"
14765
15089
  ]);
14766
- var HttpUrl3 = z61.string().url().max(2e3).refine((value) => {
15090
+ var HttpUrl3 = z62.string().url().max(2e3).refine((value) => {
14767
15091
  try {
14768
15092
  return [
14769
15093
  "http:",
@@ -14773,43 +15097,43 @@ var HttpUrl3 = z61.string().url().max(2e3).refine((value) => {
14773
15097
  return false;
14774
15098
  }
14775
15099
  }, "URL must use HTTP(S)");
14776
- var ResearchCandidateOrigin = z61.object({
14777
- type: z61.literal("research_candidate"),
15100
+ var ResearchCandidateOrigin = z62.object({
15101
+ type: z62.literal("research_candidate"),
14778
15102
  runId: ProjectId,
14779
- candidateSourceId: z61.string().min(1).max(160)
15103
+ candidateSourceId: z62.string().min(1).max(160)
14780
15104
  });
14781
- var TaskOrigin = z61.object({
14782
- type: z61.literal("task"),
15105
+ var TaskOrigin = z62.object({
15106
+ type: z62.literal("task"),
14783
15107
  taskId: ProjectId
14784
15108
  });
14785
- var ManualOrigin = z61.object({
14786
- type: z61.literal("manual"),
15109
+ var ManualOrigin = z62.object({
15110
+ type: z62.literal("manual"),
14787
15111
  clientRequestId: ProjectId,
14788
- country: z61.string().min(2).max(3).optional()
15112
+ country: z62.string().min(2).max(3).optional()
14789
15113
  });
14790
- var StartProjectInput = z61.object({
14791
- origin: z61.discriminatedUnion("type", [
15114
+ var StartProjectInput = z62.object({
15115
+ origin: z62.discriminatedUnion("type", [
14792
15116
  ResearchCandidateOrigin,
14793
15117
  TaskOrigin,
14794
15118
  ManualOrigin
14795
15119
  ]),
14796
- productName: z61.string().trim().min(1).max(240).optional(),
15120
+ productName: z62.string().trim().min(1).max(240).optional(),
14797
15121
  productUrl: HttpUrl3.optional(),
14798
- country: z61.string().min(2).max(3).optional(),
14799
- languageCode: z61.string().min(2).max(16).optional()
15122
+ country: z62.string().min(2).max(3).optional(),
15123
+ languageCode: z62.string().min(2).max(16).optional()
14800
15124
  }).strict();
14801
- var CostProjectInput = z61.object({
15125
+ var CostProjectInput = z62.object({
14802
15126
  projectId: ProjectId,
14803
- version: z61.number().int().positive(),
14804
- country: z61.string().trim().min(2).max(3),
14805
- supplierCost: z61.number().finite().positive(),
14806
- shippingCost: z61.number().finite().nonnegative(),
14807
- returnRate: z61.number().finite().min(0).lt(1),
14808
- cancellationRate: z61.number().finite().min(0).lt(1),
14809
- adsManagerCpa: z61.number().finite().nonnegative(),
14810
- desiredMargin: z61.number().finite().min(0).lt(1)
14811
- });
14812
- var GuideInput = z61.object({
15127
+ version: z62.number().int().positive(),
15128
+ country: z62.string().trim().min(2).max(3),
15129
+ supplierCost: z62.number().finite().positive(),
15130
+ shippingCost: z62.number().finite().nonnegative(),
15131
+ returnRate: z62.number().finite().min(0).lt(1),
15132
+ cancellationRate: z62.number().finite().min(0).lt(1),
15133
+ adsManagerCpa: z62.number().finite().nonnegative(),
15134
+ desiredMargin: z62.number().finite().min(0).lt(1)
15135
+ });
15136
+ var GuideInput = z62.object({
14813
15137
  projectId: ProjectId.optional()
14814
15138
  }).strict();
14815
15139
  var NEXT_ACTION_KINDS = [
@@ -14827,29 +15151,29 @@ var NEXT_ACTION_KINDS = [
14827
15151
  "repair_meta_campaign",
14828
15152
  "review_paused_campaign"
14829
15153
  ];
14830
- var DropshippingProjectNextAction = z61.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z61.object({
14831
- kind: z61.literal(kind),
14832
- href: z61.string().min(1)
15154
+ var DropshippingProjectNextAction = z62.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z62.object({
15155
+ kind: z62.literal(kind),
15156
+ href: z62.string().min(1)
14833
15157
  })));
14834
- var CostingInputSchema = z61.object({
14835
- country: z61.string().min(2).max(3),
14836
- supplierCost: z61.number().finite().positive(),
14837
- shippingCost: z61.number().finite().nonnegative(),
14838
- returnRate: z61.number().finite().min(0).lt(1),
14839
- cancellationRate: z61.number().finite().min(0).lt(1),
14840
- adsManagerCpa: z61.number().finite().nonnegative(),
14841
- desiredMargin: z61.number().finite().min(0).lt(1)
15158
+ var CostingInputSchema = z62.object({
15159
+ country: z62.string().min(2).max(3),
15160
+ supplierCost: z62.number().finite().positive(),
15161
+ shippingCost: z62.number().finite().nonnegative(),
15162
+ returnRate: z62.number().finite().min(0).lt(1),
15163
+ cancellationRate: z62.number().finite().min(0).lt(1),
15164
+ adsManagerCpa: z62.number().finite().nonnegative(),
15165
+ desiredMargin: z62.number().finite().min(0).lt(1)
14842
15166
  }).superRefine(({ returnRate, cancellationRate }, context) => {
14843
15167
  if (returnRate + cancellationRate >= 1) context.addIssue({
14844
- code: z61.ZodIssueCode.custom,
15168
+ code: z62.ZodIssueCode.custom,
14845
15169
  path: [
14846
15170
  "cancellationRate"
14847
15171
  ]
14848
15172
  });
14849
15173
  });
14850
- var FiniteCostingNumber = z61.number().finite();
14851
- var CostingResultSchema = z61.object({
14852
- country: z61.string().min(2).max(3),
15174
+ var FiniteCostingNumber = z62.number().finite();
15175
+ var CostingResultSchema = z62.object({
15176
+ country: z62.string().min(2).max(3),
14853
15177
  deliveryRate: FiniteCostingNumber,
14854
15178
  adjustedShippingCost: FiniteCostingNumber,
14855
15179
  adjustedCpa: FiniteCostingNumber,
@@ -14862,44 +15186,44 @@ var CostingResultSchema = z61.object({
14862
15186
  estimatedProfit: FiniteCostingNumber,
14863
15187
  actualMargin: FiniteCostingNumber,
14864
15188
  breakEvenAdsManagerCpa: FiniteCostingNumber,
14865
- wasPriceAutoAdjusted: z61.boolean(),
14866
- profitabilityStatus: z61.literal("healthy")
14867
- });
14868
- var RewardProgressSchema = z61.object({
14869
- campaignKey: z61.literal("dropshipping-launch-v1"),
14870
- totalCredits: z61.number().int().positive(),
14871
- earnedCredits: z61.number().int().nonnegative(),
14872
- milestones: z61.array(z61.object({
15189
+ wasPriceAutoAdjusted: z62.boolean(),
15190
+ profitabilityStatus: z62.literal("healthy")
15191
+ });
15192
+ var RewardProgressSchema = z62.object({
15193
+ campaignKey: z62.literal("dropshipping-launch-v1"),
15194
+ totalCredits: z62.number().int().positive(),
15195
+ earnedCredits: z62.number().int().nonnegative(),
15196
+ milestones: z62.array(z62.object({
14873
15197
  milestone: DropshippingRewardMilestone,
14874
- credits: z61.number().int().positive(),
14875
- available: z61.boolean().optional(),
14876
- earned: z61.boolean(),
15198
+ credits: z62.number().int().positive(),
15199
+ available: z62.boolean().optional(),
15200
+ earned: z62.boolean(),
14877
15201
  grantId: ProjectId.nullable(),
14878
- grantedAt: z61.string().datetime().nullable()
15202
+ grantedAt: z62.string().datetime().nullable()
14879
15203
  })).length(DropshippingRewardMilestone.options.length)
14880
15204
  });
14881
- var ProjectResponseSchema = z61.object({
15205
+ var ProjectResponseSchema = z62.object({
14882
15206
  id: ProjectId,
14883
15207
  taskId: ProjectId.nullable(),
14884
15208
  status: DropshippingProjectStatus,
14885
- originKey: z61.string().min(1),
15209
+ originKey: z62.string().min(1),
14886
15210
  researchRunId: ProjectId.nullable(),
14887
- candidateSourceId: z61.string().nullable(),
14888
- productName: z61.string().nullable(),
15211
+ candidateSourceId: z62.string().nullable(),
15212
+ productName: z62.string().nullable(),
14889
15213
  productUrl: HttpUrl3.nullable(),
14890
15214
  referenceImageUrl: HttpUrl3.nullable(),
14891
- targetCountry: z61.string().nullable(),
14892
- languageCode: z61.string().nullable(),
14893
- hasReferenceImage: z61.boolean(),
15215
+ targetCountry: z62.string().nullable(),
15216
+ languageCode: z62.string().nullable(),
15217
+ hasReferenceImage: z62.boolean(),
14894
15218
  costingInput: CostingInputSchema.nullable(),
14895
15219
  costingResult: CostingResultSchema.nullable(),
14896
- costingVersion: z61.number().int().positive().nullable(),
14897
- version: z61.number().int().positive(),
15220
+ costingVersion: z62.number().int().positive().nullable(),
15221
+ version: z62.number().int().positive(),
14898
15222
  phase: DropshippingProjectPhase,
14899
15223
  nextAction: DropshippingProjectNextAction.nullable(),
14900
15224
  rewards: RewardProgressSchema,
14901
- createdAt: z61.string().datetime(),
14902
- updatedAt: z61.string().datetime()
15225
+ createdAt: z62.string().datetime(),
15226
+ updatedAt: z62.string().datetime()
14903
15227
  }).passthrough();
14904
15228
  var NEXT_ACTION_GUIDANCE = {
14905
15229
  cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario.",
@@ -14948,28 +15272,38 @@ function projectText(raw) {
14948
15272
  }
14949
15273
  __name(projectText, "projectText");
14950
15274
  function registerDropshippingProjectTools(server, api) {
14951
- server.tool("start_dropshipping_project", "Inicia o reanuda un proyecto opcional de dropshipping. \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, async (input) => {
15275
+ server.tool("start_dropshipping_project", "Inicia o reanuda un proyecto opcional de dropshipping. \xDAsalo cuando el usuario quiera seguir un producto hasta publicarlo; para una operaci\xF3n puntual usa el tool directo. La creaci\xF3n es idempotente y no cobra cr\xE9ditos.", StartProjectInput.shape, CREATES_INTERNAL, async (input) => {
14952
15276
  try {
14953
15277
  return projectText(await api.postJson("/v1/dropshipping-projects", input));
14954
15278
  } catch (error) {
14955
15279
  throw translateError(error);
14956
15280
  }
14957
15281
  });
14958
- server.tool("cost_dropshipping_project", "Guarda el costeo de un proyecto guiado. Escribe solo inputs; el backend recalcula el resultado can\xF3nico y controla la versi\xF3n. Pide al usuario el costo real del proveedor: nunca lo inventes. No crea tarea ni publica nada.", CostProjectInput.shape, async (input) => {
14959
- try {
14960
- if (input.returnRate + input.cancellationRate >= 1) {
14961
- throw new Error("returnRate + cancellationRate debe ser menor que 1.");
15282
+ server.tool(
15283
+ "cost_dropshipping_project",
15284
+ "Guarda el costeo de un proyecto guiado. Escribe solo inputs; el backend recalcula el resultado can\xF3nico y controla la versi\xF3n. Pide al usuario el costo real del proveedor: nunca lo inventes. No crea tarea ni publica nada.",
15285
+ CostProjectInput.shape,
15286
+ // Overwrites the costing inputs already saved on the project — the same
15287
+ // "previous values are gone" case as edit_task_draft, so destructive.
15288
+ // idempotentHint stays false despite the PUT: the `version` guard makes a
15289
+ // blind retry FAIL rather than replay, so a host should not auto-retry it.
15290
+ OVERWRITES_INTERNAL,
15291
+ async (input) => {
15292
+ try {
15293
+ if (input.returnRate + input.cancellationRate >= 1) {
15294
+ throw new Error("returnRate + cancellationRate debe ser menor que 1.");
15295
+ }
15296
+ const { projectId, version, ...costingInput } = input;
15297
+ return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
15298
+ version,
15299
+ input: costingInput
15300
+ }));
15301
+ } catch (error) {
15302
+ throw translateError(error);
14962
15303
  }
14963
- const { projectId, version, ...costingInput } = input;
14964
- return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
14965
- version,
14966
- input: costingInput
14967
- }));
14968
- } catch (error) {
14969
- throw translateError(error);
14970
15304
  }
14971
- });
14972
- server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
15305
+ );
15306
+ server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, READS_INTERNAL, async (input) => {
14973
15307
  try {
14974
15308
  if (!input.projectId) {
14975
15309
  return asText({