@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/http.js CHANGED
@@ -896,6 +896,42 @@ var VideoTier = z5.preprocess((v) => v === "grok" ? "single_call" : v, z5.enum([
896
896
  ]));
897
897
  var DEFAULT_VIDEO_TIER = "single_call";
898
898
 
899
+ // ../../packages/types/src/image-provider.ts
900
+ var GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K = [
901
+ "4:5",
902
+ "5:4"
903
+ ];
904
+ function gptImage2SupportsTier(aspectRatio, tier) {
905
+ if (tier === "gpt-image-2:1k") return true;
906
+ return !GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(aspectRatio);
907
+ }
908
+ __name(gptImage2SupportsTier, "gptImage2SupportsTier");
909
+ var IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE = "image_tier_unsupported_for_ratio";
910
+ var IMAGE_TIER_LIST = [
911
+ "gpt-image-2:1k",
912
+ "gpt-image-2:2k",
913
+ "gpt-image-2:4k"
914
+ ];
915
+ function parseImageTier(imageModel) {
916
+ const normalized = imageModel.trim().toLowerCase();
917
+ return IMAGE_TIER_LIST.includes(normalized) ? normalized : null;
918
+ }
919
+ __name(parseImageTier, "parseImageTier");
920
+ function validateImageTierForRatio(aspectRatio, imageModel) {
921
+ if (!aspectRatio || !imageModel) return null;
922
+ const tier = parseImageTier(imageModel);
923
+ if (!tier) return null;
924
+ const ratio = aspectRatio.trim();
925
+ if (!GPT_IMAGE_2_RATIOS_BLOCKED_ABOVE_1K.includes(ratio)) return null;
926
+ if (gptImage2SupportsTier(ratio, tier)) return null;
927
+ return buildImageTierUnsupportedMessage(ratio);
928
+ }
929
+ __name(validateImageTierForRatio, "validateImageTierForRatio");
930
+ function buildImageTierUnsupportedMessage(aspectRatio) {
931
+ return `El formato ${aspectRatio} solo est\xE1 disponible en calidad est\xE1ndar (1K). Elige 1K, o cambia el formato para usar Premium.`;
932
+ }
933
+ __name(buildImageTierUnsupportedMessage, "buildImageTierUnsupportedMessage");
934
+
899
935
  // ../../packages/types/src/angle.ts
900
936
  import { z as z6 } from "zod";
901
937
  var ImageWinnerSignal = z6.enum([
@@ -2236,6 +2272,19 @@ var IngestOptions = z15.object({
2236
2272
  message
2237
2273
  });
2238
2274
  }
2275
+ const tierMessage = validateImageTierForRatio(data.aspectRatio, data.imageModel);
2276
+ if (tierMessage) {
2277
+ ctx.addIssue({
2278
+ code: z15.ZodIssueCode.custom,
2279
+ path: [
2280
+ "imageModel"
2281
+ ],
2282
+ params: {
2283
+ rule: IMAGE_TIER_UNSUPPORTED_FOR_RATIO_RULE
2284
+ },
2285
+ message: tierMessage
2286
+ });
2287
+ }
2239
2288
  });
2240
2289
  var IngestRequest = z15.object({
2241
2290
  url: z15.string().url(),
@@ -2780,6 +2829,7 @@ var ShopifyPublicationMode = z20.enum([
2780
2829
  "existing",
2781
2830
  "new"
2782
2831
  ]);
2832
+ var SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH = 160;
2783
2833
  var ShopifyPublishRequest = z20.object({
2784
2834
  shopDomain: z20.string().min(3).max(120).optional(),
2785
2835
  /**
@@ -2798,7 +2848,9 @@ var ShopifyPublishRequest = z20.object({
2798
2848
  */
2799
2849
  publicationMode: ShopifyPublicationMode.default("auto"),
2800
2850
  /** Shopify product GID, numeric id, or admin product URL. Required for existing mode. */
2801
- targetProductId: z20.string().trim().min(1).max(300).optional()
2851
+ targetProductId: z20.string().trim().min(1).max(300).optional(),
2852
+ announcementBarEnabled: z20.boolean().optional(),
2853
+ announcementBarText: z20.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
2802
2854
  }).superRefine((data, ctx) => {
2803
2855
  if (data.publicationMode === "existing" && !data.targetProductId) {
2804
2856
  ctx.addIssue({
@@ -2809,6 +2861,24 @@ var ShopifyPublishRequest = z20.object({
2809
2861
  message: "Selecciona un producto de Shopify."
2810
2862
  });
2811
2863
  }
2864
+ if (data.announcementBarEnabled && !data.announcementBarText) {
2865
+ ctx.addIssue({
2866
+ code: z20.ZodIssueCode.custom,
2867
+ path: [
2868
+ "announcementBarText"
2869
+ ],
2870
+ message: "Escribe el texto del anuncio."
2871
+ });
2872
+ }
2873
+ if (!data.announcementBarEnabled && data.announcementBarText) {
2874
+ ctx.addIssue({
2875
+ code: z20.ZodIssueCode.custom,
2876
+ path: [
2877
+ "announcementBarText"
2878
+ ],
2879
+ message: "Activa el anuncio para usar texto."
2880
+ });
2881
+ }
2812
2882
  });
2813
2883
  var ShopifyPublicationStatus = z20.enum([
2814
2884
  "pending",
@@ -2891,6 +2961,10 @@ var CreateShopifyBillingChargeRequest = z20.object({
2891
2961
  var CancelShopifySubscriptionRequest = z20.object({
2892
2962
  subscriptionId: z20.string().min(1)
2893
2963
  });
2964
+ var ShopifyAnnouncementBarState = z20.object({
2965
+ enabled: z20.boolean(),
2966
+ text: z20.string().max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
2967
+ });
2894
2968
  var LandingHealthResponse = z20.object({
2895
2969
  taskId: z20.string(),
2896
2970
  state: z20.enum([
@@ -2932,9 +3006,20 @@ var ApproveAnglesRequest = z21.object({
2932
3006
  "mixed"
2933
3007
  ]).optional()
2934
3008
  });
3009
+ var ApproveAnglesRefusalReason = z21.enum([
3010
+ "photo_review_pending",
3011
+ "photos_not_ready",
3012
+ "reference_out_of_date"
3013
+ ]);
2935
3014
  var ApproveAnglesResponse = z21.object({
2936
3015
  success: z21.boolean(),
2937
- taskId: z21.string().uuid()
3016
+ taskId: z21.string().uuid(),
3017
+ reason: ApproveAnglesRefusalReason.optional()
3018
+ });
3019
+ var IngestResumeRefusalResponse = z21.object({
3020
+ success: z21.literal(false),
3021
+ taskId: z21.string().uuid(),
3022
+ reason: ApproveAnglesRefusalReason.optional()
2938
3023
  });
2939
3024
  var GenerateSuggestionsResponse = z21.object({
2940
3025
  painPoints: z21.array(z21.string()),
@@ -3050,7 +3135,8 @@ var ApproveVideoAnglesRequest = z21.object({
3050
3135
  });
3051
3136
  var ApproveVideoAnglesResponse = z21.object({
3052
3137
  success: z21.boolean(),
3053
- taskId: z21.string().uuid()
3138
+ taskId: z21.string().uuid(),
3139
+ reason: ApproveAnglesRefusalReason.optional()
3054
3140
  });
3055
3141
  var SaveVideoDraftRequest = z21.object({
3056
3142
  /** Global creative direction for all videos. Not spoken dialogue. */
@@ -3082,7 +3168,8 @@ var ApproveImageAnglesRequest = z21.object({
3082
3168
  });
3083
3169
  var ApproveImageAnglesResponse = z21.object({
3084
3170
  success: z21.boolean(),
3085
- taskId: z21.string().uuid()
3171
+ taskId: z21.string().uuid(),
3172
+ reason: ApproveAnglesRefusalReason.optional()
3086
3173
  });
3087
3174
  var SaveImageDraftRequest = z21.object({
3088
3175
  customAngle: z21.string().max(480).optional(),
@@ -3439,6 +3526,10 @@ var MVPResult = z24.object({
3439
3526
  videoAngleCount: z24.number().int().min(1).max(3).optional(),
3440
3527
  adCreative: AdCreative.optional(),
3441
3528
  shopifyPageUrl: z24.string().url().optional(),
3529
+ /** What the announcement bar looks like ON THE PUBLISHED landing right now.
3530
+ * The publish dialog prefills from this: without it the checkbox always opens
3531
+ * unchecked, and republishing anything would read as "remove the bar". */
3532
+ shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
3442
3533
  // Pain points and benefits for image wizard pre-filling
3443
3534
  painPoints: z24.string().optional(),
3444
3535
  benefits: z24.string().optional(),
@@ -3564,7 +3655,19 @@ var StatusResponse = z24.object({
3564
3655
  "done",
3565
3656
  "error"
3566
3657
  ]),
3658
+ /** ENGINEERING string (`Task.error`). Never render this to a seller. */
3567
3659
  error: z24.string().optional(),
3660
+ /**
3661
+ * SELLER-FACING failure copy, in Spanish, already decided by the backend —
3662
+ * render it VERBATIM. Present only for `status: 'error'`.
3663
+ *
3664
+ * Clients used to receive only `error` and guess at it with a local regex
3665
+ * table; measured 2026-08-18 over 187 failed tasks in 60 days, that guess
3666
+ * produced contentless copy ("Error desconocido." / "Error interno") for 123
3667
+ * of them. Populated from `Task.userMessage`, falling back to the same
3668
+ * resolver for rows written before that column existed.
3669
+ */
3670
+ userMessage: z24.string().optional(),
3568
3671
  startedAt: z24.string().datetime().optional(),
3569
3672
  updatedAt: z24.string().datetime().optional(),
3570
3673
  stageStatuses: StageStatusSnapshot,
@@ -3575,6 +3678,10 @@ var StatusResponse = z24.object({
3575
3678
  * campaign target geo when creating Meta ads from this task. */
3576
3679
  targetCountry: z24.string().optional(),
3577
3680
  shopifyPageUrl: z24.string().url().optional(),
3681
+ /** What the announcement bar looks like ON THE PUBLISHED landing right now.
3682
+ * The publish dialog prefills from this: without it the checkbox always opens
3683
+ * unchecked, and republishing anything would read as "remove the bar". */
3684
+ shopifyAnnouncementBar: ShopifyAnnouncementBarState.optional(),
3578
3685
  shopifyShops: z24.array(z24.object({
3579
3686
  shopDomain: z24.string(),
3580
3687
  primaryDomain: z24.string().nullable()
@@ -3585,6 +3692,10 @@ var StatusResponse = z24.object({
3585
3692
  requiresAction: z24.boolean().optional(),
3586
3693
  /** True when the task needs the user's attention rather than passive progress. */
3587
3694
  needsReview: z24.boolean().optional(),
3695
+ /** True when an open gate exists that somebody can actually ANSWER. A strictly
3696
+ * narrower fact than `needsReview` — see the identical field on `TaskSummary`
3697
+ * below for why conflating the two produced a call to action that led nowhere. */
3698
+ hasAnswerableGate: z24.boolean().optional(),
3588
3699
  /** Which landing pipeline produced this task's landing. 'code' tasks have no
3589
3700
  * classic LandingImage rows — the result lives in the inline code editor. */
3590
3701
  landingMode: LandingModeSchema.optional(),
@@ -3655,6 +3766,27 @@ var TaskSummary = z24.object({
3655
3766
  /** True when user action is ACTUALLY needed (angle review or script review ready).
3656
3767
  * Use this for status display to show "Revisar" vs "Progreso". */
3657
3768
  needsReview: z24.boolean().optional(),
3769
+ /**
3770
+ * True when an open gate exists that somebody can actually ANSWER.
3771
+ *
3772
+ * DIFFERENT FACT from `needsReview`, and the difference is the bug. `needsReview`
3773
+ * means "this task is waiting on the seller"; the list badge renders «Revisar»
3774
+ * from it. But a task whose dispatch was lost is also "waiting", with no question
3775
+ * open anywhere — and so is the autopilot angle wait, whose resume button is
3776
+ * measurably INERT (`resumeAfterAngles` CASes on a flag that is false there, so
3777
+ * it returns 200 and dispatches nothing). Both render «Revisar» today and both
3778
+ * lead the seller nowhere. The owner spent hours one night convinced the platform
3779
+ * was frozen, chasing a ghost the badge itself invented.
3780
+ *
3781
+ * Derived from the reconciler's gate registry, NOT from a hand-maintained flag:
3782
+ * being a registry entry is what "answerable" means, because every entry declares
3783
+ * a decider and a deadline at compile time. A gate added tomorrow reports itself
3784
+ * here on its own. See `answerableGateIdsFor` in
3785
+ * apps/api/src/modules/cron/gate-reconciler/gate-registry.ts.
3786
+ *
3787
+ * Render «Revisar» on THIS, not on `needsReview`.
3788
+ */
3789
+ hasAnswerableGate: z24.boolean().optional(),
3658
3790
  createdAt: z24.string().datetime().optional(),
3659
3791
  startedAt: z24.string().datetime().optional(),
3660
3792
  updatedAt: z24.string().datetime().optional(),
@@ -5446,6 +5578,10 @@ var TikTokCreateCampaignResponseSchema = z35.object({
5446
5578
  adsManagerUrl: z35.string().optional()
5447
5579
  });
5448
5580
 
5581
+ // ../../packages/types/src/pricing-gemini-omni.ts
5582
+ var GEMINI_OMNI_FLAT_OUR_COST_CENTS = 84;
5583
+ var GEMINI_OMNI_FLAT_USER_PRICE_CENTS = 168;
5584
+
5449
5585
  // ../../packages/types/src/pricing.ts
5450
5586
  var CREDITS_PER_USD_CENT = 10;
5451
5587
  var CREDITS_PER_DOLLAR = 100 * CREDITS_PER_USD_CENT;
@@ -5854,6 +5990,18 @@ var DEFAULT_PRICING = {
5854
5990
  ourCostCents: 120,
5855
5991
  userPriceCents: 240
5856
5992
  },
5993
+ // Gemini Omni Video (gemini-omni-video) — Mix primary generator since
5994
+ // 2026-08-18. FLAT per generation: 168 KIE credits = $0.84, identical at
5995
+ // 720p and 1080p and independent of the 3.5-10s trim window. Do NOT add
5996
+ // per-second overrides for this key — resolveSuccessBilling bills it flat.
5997
+ //
5998
+ // Shares the constants with the pre-submit ESTIMATOR (mix-cost.ts) rather
5999
+ // than repeating the numbers: a literal here that drifted from the estimator
6000
+ // would quote one price and charge another on every mix.
6001
+ "kie:gemini-omni-video": {
6002
+ ourCostCents: GEMINI_OMNI_FLAT_OUR_COST_CENTS,
6003
+ userPriceCents: GEMINI_OMNI_FLAT_USER_PRICE_CENTS
6004
+ },
5857
6005
  "kie:video-lipsync-generation": {
5858
6006
  ourCostCents: 45,
5859
6007
  userPriceCents: 90
@@ -7817,7 +7965,57 @@ var FeatureSpec = z36.object({
7817
7965
  });
7818
7966
 
7819
7967
  // ../../packages/types/src/mix.ts
7968
+ import { z as z38 } from "zod";
7969
+
7970
+ // ../../packages/types/src/mix-degradation.ts
7820
7971
  import { z as z37 } from "zod";
7972
+ var MIX_DEGRADATION_REASONS = [
7973
+ /**
7974
+ * At least one scene the mix PLANNED is missing from the delivered video, so
7975
+ * the result is shorter than the source. Set at assemble time by comparing
7976
+ * the concat set against the planned scene set — the scene's own code (if it
7977
+ * had one) never reaches the mix-level rollup, which only sees scenes that
7978
+ * SUCCEEDED, and that is exactly how a live 17.00s-of-20.00s delivery
7979
+ * shipped with degradedReason NULL and no banner at all.
7980
+ *
7981
+ * ORDERING: FIRST, above every refusal code. A short delivery must never be
7982
+ * MASKED. It used to sit below the three content refusals on the theory that
7983
+ * a refusal names something the user can change AND explains the missing
7984
+ * scene — but it does neither reliably: in a 3-scene mix where scene 1 fails
7985
+ * outright and scene 2 ships through Wan after a reference refusal, the
7986
+ * refusal copy reads "we used a lower-quality alternative", a claim about
7987
+ * QUALITY that says nothing about the video being shorter than what was paid
7988
+ * for, and that is actively wrong about the dropped scene (for which we used
7989
+ * nothing). Every other code in this list describes a scene that DID ship.
7990
+ */
7991
+ "scenes_dropped",
7992
+ /** Google's safety filter rejected the user's reference photo. Deterministic. */
7993
+ "reference_rejected",
7994
+ /** Google refused the wording of the change description. */
7995
+ "prompt_rejected",
7996
+ /**
7997
+ * The provider refused the content but its message names neither the
7998
+ * reference nor the wording, so we cannot say which. Distinct from
7999
+ * 'provider_unavailable' on purpose: "unavailable" invites the user to retry
8000
+ * unchanged, which can never succeed against a content refusal and only
8001
+ * burns their credits. This code tells them something must change.
8002
+ */
8003
+ "content_rejected",
8004
+ /**
8005
+ * The scene's length fell outside Gemini's usable 3.5s-10s trim window —
8006
+ * EITHER bound. Deliberately ONE code for both: the user controls neither
8007
+ * (scene segmentation is entirely ours), so "too short" and "too long" are
8008
+ * not differently actionable, and one honest explanation beats two that
8009
+ * both amount to "our segmentation produced a length the primary generator
8010
+ * cannot take".
8011
+ */
8012
+ "scene_length_unsupported",
8013
+ /** Transient provider failures exhausted the budget. */
8014
+ "provider_unavailable"
8015
+ ];
8016
+ var MixDegradationReasonSchema = z37.enum(MIX_DEGRADATION_REASONS);
8017
+
8018
+ // ../../packages/types/src/mix.ts
7821
8019
  var MEDIA_TYPES = [
7822
8020
  "video",
7823
8021
  "image"
@@ -7855,38 +8053,38 @@ function isPublicHttpsUrl(raw) {
7855
8053
  return true;
7856
8054
  }
7857
8055
  __name(isPublicHttpsUrl, "isPublicHttpsUrl");
7858
- var PublicHttpsUrl = z37.string().url().refine(isPublicHttpsUrl, {
8056
+ var PublicHttpsUrl = z38.string().url().refine(isPublicHttpsUrl, {
7859
8057
  message: "must be a public HTTPS URL"
7860
8058
  });
7861
- var InternalAssetPath = z37.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
7862
- var MixImageUrl = z37.union([
8059
+ var InternalAssetPath = z38.string().regex(/^\/(?:api|v1)\/assets\/[A-Za-z0-9._~%/-]+$/, "must be a public HTTPS URL or an internal asset path");
8060
+ var MixImageUrl = z38.union([
7863
8061
  PublicHttpsUrl,
7864
8062
  InternalAssetPath
7865
8063
  ]);
7866
- var MixAxes = z37.object({
7867
- product: z37.boolean(),
7868
- person: z37.boolean(),
7869
- setting: z37.boolean()
7870
- });
7871
- var MixReferences = z37.object({
7872
- product: z37.array(PublicHttpsUrl).max(3, "at most 3 product references"),
7873
- person: z37.array(PublicHttpsUrl).max(3, "at most 3 person references"),
7874
- setting: z37.array(PublicHttpsUrl).max(3, "at most 3 setting references")
7875
- });
7876
- var MixImageEdit = z37.object({
7877
- instruction: z37.string().min(1).max(600),
7878
- imageUrls: z37.array(MixImageUrl).min(1).max(3)
7879
- });
7880
- var MixRequest = z37.object({
7881
- mediaType: z37.enum(MEDIA_TYPES),
7882
- originalUrl: z37.union([
8064
+ var MixAxes = z38.object({
8065
+ product: z38.boolean(),
8066
+ person: z38.boolean(),
8067
+ setting: z38.boolean()
8068
+ });
8069
+ var MixReferences = z38.object({
8070
+ product: z38.array(PublicHttpsUrl).max(3, "at most 3 product references"),
8071
+ person: z38.array(PublicHttpsUrl).max(3, "at most 3 person references"),
8072
+ setting: z38.array(PublicHttpsUrl).max(3, "at most 3 setting references")
8073
+ });
8074
+ var MixImageEdit = z38.object({
8075
+ instruction: z38.string().min(1).max(600),
8076
+ imageUrls: z38.array(MixImageUrl).min(1).max(3)
8077
+ });
8078
+ var MixRequest = z38.object({
8079
+ mediaType: z38.enum(MEDIA_TYPES),
8080
+ originalUrl: z38.union([
7883
8081
  PublicHttpsUrl,
7884
8082
  InternalAssetPath
7885
8083
  ]),
7886
8084
  axes: MixAxes,
7887
8085
  references: MixReferences,
7888
- personText: z37.string().max(500).optional(),
7889
- settingText: z37.string().max(500).optional(),
8086
+ personText: z38.string().max(500).optional(),
8087
+ settingText: z38.string().max(500).optional(),
7890
8088
  /**
7891
8089
  * Optional public URL of a user-uploaded audio file (mp3/wav). Wan Video
7892
8090
  * Edit has no audio input and the mix never lipsyncs: the Wan output is
@@ -7895,11 +8093,11 @@ var MixRequest = z37.object({
7895
8093
  * (mute + overlay). No per-scene slicing, no `reference_audio_urls`.
7896
8094
  */
7897
8095
  audioUrl: PublicHttpsUrl.optional(),
7898
- resolution: z37.enum(RESOLUTIONS).default("720p"),
7899
- originalDurationSec: z37.number().positive().max(600).optional(),
8096
+ resolution: z38.enum(RESOLUTIONS).default("720p"),
8097
+ originalDurationSec: z38.number().positive().max(600).optional(),
7900
8098
  image: MixImageEdit.optional(),
7901
- sourceTaskId: z37.string().max(64).optional(),
7902
- sourceCreativeId: z37.string().max(64).optional()
8099
+ sourceTaskId: z38.string().max(64).optional(),
8100
+ sourceCreativeId: z38.string().max(64).optional()
7903
8101
  }).superRefine((req, ctx) => {
7904
8102
  if (req.mediaType === "image") {
7905
8103
  if (!req.image) {
@@ -7933,18 +8131,18 @@ var MixRequest = z37.object({
7933
8131
  });
7934
8132
  }
7935
8133
  });
7936
- var ReferenceMap = z37.object({
7937
- product: z37.object({
7938
- start: z37.number().int(),
7939
- end: z37.number().int()
8134
+ var ReferenceMap = z38.object({
8135
+ product: z38.object({
8136
+ start: z38.number().int(),
8137
+ end: z38.number().int()
7940
8138
  }).optional(),
7941
- person: z37.object({
7942
- start: z37.number().int(),
7943
- end: z37.number().int()
8139
+ person: z38.object({
8140
+ start: z38.number().int(),
8141
+ end: z38.number().int()
7944
8142
  }).optional(),
7945
- setting: z37.object({
7946
- start: z37.number().int(),
7947
- end: z37.number().int()
8143
+ setting: z38.object({
8144
+ start: z38.number().int(),
8145
+ end: z38.number().int()
7948
8146
  }).optional()
7949
8147
  });
7950
8148
  var SCENE_CLIP_TYPES = [
@@ -7960,39 +8158,39 @@ var INSTANCE_CARDINALITIES = [
7960
8158
  "multiple_same",
7961
8159
  "multiple_distinct"
7962
8160
  ];
7963
- var SceneSegment = z37.object({
7964
- index: z37.number().int().positive(),
7965
- startTs: z37.number().nonnegative(),
7966
- endTs: z37.number().positive(),
7967
- seedanceDuration: z37.number().int().min(4).max(15),
8161
+ var SceneSegment = z38.object({
8162
+ index: z38.number().int().positive(),
8163
+ startTs: z38.number().nonnegative(),
8164
+ endTs: z38.number().positive(),
8165
+ seedanceDuration: z38.number().int().min(4).max(15),
7968
8166
  // Pass 1 / Pass 2 outputs (all optional — populated when the rich analyzer
7969
8167
  // runs and empty on fallback).
7970
- action: z37.string().optional(),
7971
- motion: z37.string().optional(),
7972
- clipType: z37.enum(SCENE_CLIP_TYPES).optional(),
7973
- hasCharacter: z37.boolean().optional(),
7974
- faceVisible: z37.boolean().optional(),
8168
+ action: z38.string().optional(),
8169
+ motion: z38.string().optional(),
8170
+ clipType: z38.enum(SCENE_CLIP_TYPES).optional(),
8171
+ hasCharacter: z38.boolean().optional(),
8172
+ faceVisible: z38.boolean().optional(),
7975
8173
  // Pass 2 refinement — rich per-scene metadata used by buildMixPrompt.
7976
- startState: z37.string().optional(),
7977
- endState: z37.string().optional(),
7978
- cameraMovement: z37.string().optional(),
7979
- emotion: z37.string().optional(),
7980
- productPlacement: z37.string().optional(),
7981
- productInteraction: z37.string().optional(),
7982
- settingDescription: z37.string().optional(),
7983
- dialogue: z37.string().optional(),
8174
+ startState: z38.string().optional(),
8175
+ endState: z38.string().optional(),
8176
+ cameraMovement: z38.string().optional(),
8177
+ emotion: z38.string().optional(),
8178
+ productPlacement: z38.string().optional(),
8179
+ productInteraction: z38.string().optional(),
8180
+ settingDescription: z38.string().optional(),
8181
+ dialogue: z38.string().optional(),
7984
8182
  // Main vs extras / noise (our addition on top of the script's schema).
7985
- mainPersonDescription: z37.string().optional(),
7986
- mainProductDescription: z37.string().optional(),
7987
- personInstances: z37.enum(INSTANCE_CARDINALITIES).optional(),
7988
- productInstances: z37.enum(INSTANCE_CARDINALITIES).optional(),
8183
+ mainPersonDescription: z38.string().optional(),
8184
+ mainProductDescription: z38.string().optional(),
8185
+ personInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
8186
+ productInstances: z38.enum(INSTANCE_CARDINALITIES).optional(),
7989
8187
  /**
7990
8188
  * Seconds of prev-clipType content absorbed at the start of this scene by
7991
8189
  * Pass 2.7's clipType boundary margin. When > 0 and the scene is a
7992
8190
  * pass-through (no Seedance), the concatenator prepends a held-still
7993
8191
  * preamble (nano-banana-swapped first frame) to hide the leak.
7994
8192
  */
7995
- absorbedMarginSec: z37.number().nonnegative().optional(),
8193
+ absorbedMarginSec: z38.number().nonnegative().optional(),
7996
8194
  /**
7997
8195
  * True when the scene shows post-production graphics baked into the frame:
7998
8196
  * caption/subtitle text, infographic overlay cards, or inset
@@ -8001,33 +8199,39 @@ var SceneSegment = z37.object({
8001
8199
  * (mentioning nonexistent overlays gives the generator license to
8002
8200
  * fabricate them).
8003
8201
  */
8004
- hasOverlayGraphics: z37.boolean().optional()
8202
+ hasOverlayGraphics: z38.boolean().optional()
8005
8203
  });
8006
- var MixSceneResult = z37.object({
8007
- index: z37.number().int().positive(),
8008
- status: z37.enum([
8204
+ var MixSceneResult = z38.object({
8205
+ index: z38.number().int().positive(),
8206
+ status: z38.enum([
8009
8207
  "queued",
8010
8208
  "submitted",
8011
8209
  "done",
8012
8210
  "failed"
8013
8211
  ]),
8014
- seedanceTaskId: z37.string().optional(),
8015
- videoUrl: z37.string().url().optional(),
8016
- error: z37.string().optional()
8017
- });
8018
- var MixResult = z37.object({
8019
- mixTaskId: z37.string().uuid(),
8020
- status: z37.enum([
8212
+ seedanceTaskId: z38.string().optional(),
8213
+ videoUrl: z38.string().url().optional(),
8214
+ error: z38.string().optional(),
8215
+ degradedReason: MixDegradationReasonSchema.optional()
8216
+ });
8217
+ var MixResult = z38.object({
8218
+ mixTaskId: z38.string().uuid(),
8219
+ status: z38.enum([
8021
8220
  "queued",
8022
8221
  "processing",
8023
8222
  "done",
8024
8223
  "error"
8025
8224
  ]),
8026
- resultUrl: z37.string().url().optional(),
8027
- scenes: z37.array(MixSceneResult).default([]),
8028
- failReason: z37.string().optional()
8225
+ resultUrl: z38.string().url().optional(),
8226
+ scenes: z38.array(MixSceneResult).default([]),
8227
+ failReason: z38.string().optional(),
8228
+ degradedReason: MixDegradationReasonSchema.optional()
8029
8229
  });
8030
8230
 
8231
+ // ../../packages/types/src/mix-cost.ts
8232
+ var MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND = 1;
8233
+ var MAX_BILLABLE_GENERATIONS_PER_BLOCK = 1 + MAX_QA_RETRIES_PER_SCENE_UPPER_BOUND;
8234
+
8031
8235
  // ../../packages/types/src/product-delivery-format.ts
8032
8236
  var DELIVERY_FAMILIES = [
8033
8237
  "solid_dose",
@@ -8116,41 +8320,41 @@ var DECLARATION = DELIVERY_FAMILIES.map((family) => [
8116
8320
  ]);
8117
8321
 
8118
8322
  // ../../packages/types/src/task-type.ts
8119
- import { z as z38 } from "zod";
8120
- var TaskTypeSchema = z38.enum([
8323
+ import { z as z39 } from "zod";
8324
+ var TaskTypeSchema = z39.enum([
8121
8325
  "product",
8122
8326
  "service"
8123
8327
  ]);
8124
8328
 
8125
8329
  // ../../packages/types/src/landing-regeneration.ts
8126
- import { z as z39 } from "zod";
8127
- var LandingRegenerationStatus = z39.enum([
8330
+ import { z as z40 } from "zod";
8331
+ var LandingRegenerationStatus = z40.enum([
8128
8332
  "queued",
8129
8333
  "processing",
8130
8334
  "published",
8131
8335
  "failed"
8132
8336
  ]);
8133
- var RegenerateLandingRoleResponse = z39.object({
8134
- ok: z39.literal(true),
8337
+ var RegenerateLandingRoleResponse = z40.object({
8338
+ ok: z40.literal(true),
8135
8339
  role: LandingRole,
8136
- promptIndex: z39.number().int().nonnegative(),
8137
- operationId: z39.string().min(1)
8340
+ promptIndex: z40.number().int().nonnegative(),
8341
+ operationId: z40.string().min(1)
8138
8342
  });
8139
- var LandingRegenerationStatusResponse = z39.object({
8140
- ok: z39.literal(true),
8141
- operationId: z39.string().min(1),
8343
+ var LandingRegenerationStatusResponse = z40.object({
8344
+ ok: z40.literal(true),
8345
+ operationId: z40.string().min(1),
8142
8346
  role: LandingRole,
8143
8347
  status: LandingRegenerationStatus,
8144
- refunded: z39.boolean(),
8145
- failureReason: z39.string().nullable(),
8146
- publishedAssetKey: z39.string().nullable(),
8147
- updatedAt: z39.string().datetime()
8348
+ refunded: z40.boolean(),
8349
+ failureReason: z40.string().nullable(),
8350
+ publishedAssetKey: z40.string().nullable(),
8351
+ updatedAt: z40.string().datetime()
8148
8352
  });
8149
8353
 
8150
8354
  // ../../packages/types/src/video-edit.ts
8151
- import { z as z40 } from "zod";
8355
+ import { z as z41 } from "zod";
8152
8356
  var MAX_SEGMENT_SECONDS = 3;
8153
- var VideoEditStatusSchema = z40.enum([
8357
+ var VideoEditStatusSchema = z41.enum([
8154
8358
  "pending",
8155
8359
  "processing",
8156
8360
  "assembling",
@@ -8159,9 +8363,9 @@ var VideoEditStatusSchema = z40.enum([
8159
8363
  "discarded",
8160
8364
  "failed"
8161
8365
  ]);
8162
- var VideoEditTimeSectionSchema = z40.object({
8163
- startSec: z40.number().min(0),
8164
- endSec: z40.number().positive()
8366
+ var VideoEditTimeSectionSchema = z41.object({
8367
+ startSec: z41.number().min(0),
8368
+ endSec: z41.number().positive()
8165
8369
  }).refine((v) => v.endSec > v.startSec, {
8166
8370
  message: "endSec must be after startSec",
8167
8371
  path: [
@@ -8173,45 +8377,45 @@ var VideoEditTimeSectionSchema = z40.object({
8173
8377
  "endSec"
8174
8378
  ]
8175
8379
  });
8176
- var VideoEditRequestSchema = z40.object({
8177
- comment: z40.string().trim().min(1).max(2e3),
8178
- sections: z40.array(VideoEditTimeSectionSchema).min(1).optional()
8380
+ var VideoEditRequestSchema = z41.object({
8381
+ comment: z41.string().trim().min(1).max(2e3),
8382
+ sections: z41.array(VideoEditTimeSectionSchema).min(1).optional()
8179
8383
  });
8180
- var VideoEditModeSchema = z40.enum([
8384
+ var VideoEditModeSchema = z41.enum([
8181
8385
  "wan_edit",
8182
8386
  "regenerate"
8183
8387
  ]);
8184
- var VideoRegenerationEngineSchema = z40.enum([
8388
+ var VideoRegenerationEngineSchema = z41.enum([
8185
8389
  "free_text",
8186
8390
  "free_image",
8187
8391
  "single_call",
8188
8392
  "multi_shot"
8189
8393
  ]);
8190
- var VideoRegenerationRouteFingerprintSchema = z40.string().trim().min(1).max(128);
8191
- var RegenerateEditRequestSchema = z40.object({
8192
- angleKey: z40.string().min(1),
8193
- comment: z40.string().min(1).max(2e3)
8394
+ var VideoRegenerationRouteFingerprintSchema = z41.string().trim().min(1).max(128);
8395
+ var RegenerateEditRequestSchema = z41.object({
8396
+ angleKey: z41.string().min(1),
8397
+ comment: z41.string().min(1).max(2e3)
8194
8398
  });
8195
8399
 
8196
8400
  // ../../packages/types/src/uploaded-asset.ts
8197
- import { z as z41 } from "zod";
8198
- var UploadedAssetMediaKind = z41.enum([
8401
+ import { z as z42 } from "zod";
8402
+ var UploadedAssetMediaKind = z42.enum([
8199
8403
  "image",
8200
8404
  "video"
8201
8405
  ]);
8202
- var UploadedAssetSessionMediaKind = z41.enum([
8406
+ var UploadedAssetSessionMediaKind = z42.enum([
8203
8407
  "image",
8204
8408
  "video",
8205
8409
  "mixed"
8206
8410
  ]);
8207
- var UploadedAssetRole = z41.enum([
8411
+ var UploadedAssetRole = z42.enum([
8208
8412
  "media",
8209
8413
  "thumbnail"
8210
8414
  ]);
8211
8415
  var UPLOADED_ASSET_MAX_FILES = 10;
8212
8416
  var UPLOADED_ASSET_IMAGE_MAX_FILES = 5;
8213
- var UploadedAssetId = z41.string().uuid();
8214
- var HttpsUrl = z41.string().url().refine((value) => {
8417
+ var UploadedAssetId = z42.string().uuid();
8418
+ var HttpsUrl = z42.string().url().refine((value) => {
8215
8419
  try {
8216
8420
  const url = new URL(value);
8217
8421
  return url.protocol === "https:" && !url.username && !url.password;
@@ -8219,23 +8423,23 @@ var HttpsUrl = z41.string().url().refine((value) => {
8219
8423
  return false;
8220
8424
  }
8221
8425
  }, "URL must use https");
8222
- var UtcDateTime = z41.string().datetime().refine((value) => {
8426
+ var UtcDateTime = z42.string().datetime().refine((value) => {
8223
8427
  const parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/.exec(value);
8224
8428
  const timestamp = Date.parse(value);
8225
8429
  if (!parts || Number.isNaN(timestamp)) return false;
8226
8430
  const date = new Date(timestamp);
8227
8431
  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]);
8228
8432
  }, "datetime must be a valid UTC calendar instant");
8229
- var UploadedAssetRef = z41.object({
8433
+ var UploadedAssetRef = z42.object({
8230
8434
  assetId: UploadedAssetId
8231
8435
  }).strict();
8232
8436
  var UploadedAssetCompletion = UploadedAssetRef.extend({
8233
8437
  mediaKind: UploadedAssetMediaKind,
8234
8438
  assetRole: UploadedAssetRole
8235
8439
  }).strict();
8236
- var UploadedAssetMetadata = z41.object({
8440
+ var UploadedAssetMetadata = z42.object({
8237
8441
  assetId: UploadedAssetId,
8238
- ownerId: z41.string().min(1),
8442
+ ownerId: z42.string().min(1),
8239
8443
  mediaKind: UploadedAssetMediaKind,
8240
8444
  assetRole: UploadedAssetRole,
8241
8445
  createdAt: UtcDateTime,
@@ -8243,7 +8447,7 @@ var UploadedAssetMetadata = z41.object({
8243
8447
  }).strict().superRefine((value, ctx) => {
8244
8448
  if (Date.parse(value.expiresAt) <= Date.parse(value.createdAt)) {
8245
8449
  ctx.addIssue({
8246
- code: z41.ZodIssueCode.custom,
8450
+ code: z42.ZodIssueCode.custom,
8247
8451
  path: [
8248
8452
  "expiresAt"
8249
8453
  ],
@@ -8251,27 +8455,27 @@ var UploadedAssetMetadata = z41.object({
8251
8455
  });
8252
8456
  }
8253
8457
  });
8254
- var MediaSourceRef = z41.object({
8458
+ var MediaSourceRef = z42.object({
8255
8459
  url: HttpsUrl.optional(),
8256
- path: z41.string().min(1).optional(),
8460
+ path: z42.string().min(1).optional(),
8257
8461
  assetId: UploadedAssetId.optional()
8258
8462
  }).strict().superRefine((value, ctx) => {
8259
8463
  const sourceCount = Number(value.url !== void 0) + Number(value.path !== void 0) + Number(value.assetId !== void 0);
8260
8464
  if (sourceCount !== 1) {
8261
8465
  ctx.addIssue({
8262
- code: z41.ZodIssueCode.custom,
8466
+ code: z42.ZodIssueCode.custom,
8263
8467
  message: "media source needs exactly one of url, path, or assetId"
8264
8468
  });
8265
8469
  }
8266
8470
  });
8267
- var UploadedAssetSessionRequest = z41.object({
8471
+ var UploadedAssetSessionRequest = z42.object({
8268
8472
  mediaKind: UploadedAssetSessionMediaKind,
8269
8473
  assetRole: UploadedAssetRole.default("media"),
8270
- maxFiles: z41.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
8474
+ maxFiles: z42.number().int().min(1).max(UPLOADED_ASSET_MAX_FILES).optional()
8271
8475
  }).strict().superRefine((value, ctx) => {
8272
8476
  if (value.mediaKind === "mixed" && value.assetRole === "thumbnail") {
8273
8477
  ctx.addIssue({
8274
- code: z41.ZodIssueCode.custom,
8478
+ code: z42.ZodIssueCode.custom,
8275
8479
  path: [
8276
8480
  "mediaKind"
8277
8481
  ],
@@ -8280,7 +8484,7 @@ var UploadedAssetSessionRequest = z41.object({
8280
8484
  }
8281
8485
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
8282
8486
  ctx.addIssue({
8283
- code: z41.ZodIssueCode.custom,
8487
+ code: z42.ZodIssueCode.custom,
8284
8488
  path: [
8285
8489
  "maxFiles"
8286
8490
  ],
@@ -8289,7 +8493,7 @@ var UploadedAssetSessionRequest = z41.object({
8289
8493
  }
8290
8494
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > UPLOADED_ASSET_IMAGE_MAX_FILES) {
8291
8495
  ctx.addIssue({
8292
- code: z41.ZodIssueCode.custom,
8496
+ code: z42.ZodIssueCode.custom,
8293
8497
  path: [
8294
8498
  "maxFiles"
8295
8499
  ],
@@ -8298,7 +8502,7 @@ var UploadedAssetSessionRequest = z41.object({
8298
8502
  }
8299
8503
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
8300
8504
  ctx.addIssue({
8301
- code: z41.ZodIssueCode.custom,
8505
+ code: z42.ZodIssueCode.custom,
8302
8506
  path: [
8303
8507
  "mediaKind"
8304
8508
  ],
@@ -8306,32 +8510,32 @@ var UploadedAssetSessionRequest = z41.object({
8306
8510
  });
8307
8511
  }
8308
8512
  });
8309
- var UploadedAssetSessionStatus = z41.discriminatedUnion("status", [
8310
- z41.object({
8311
- status: z41.literal("pending"),
8312
- assets: z41.array(UploadedAssetCompletion).length(0)
8513
+ var UploadedAssetSessionStatus = z42.discriminatedUnion("status", [
8514
+ z42.object({
8515
+ status: z42.literal("pending"),
8516
+ assets: z42.array(UploadedAssetCompletion).length(0)
8313
8517
  }).strict(),
8314
- z41.object({
8315
- status: z41.literal("uploaded"),
8316
- assets: z41.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
8518
+ z42.object({
8519
+ status: z42.literal("uploaded"),
8520
+ assets: z42.array(UploadedAssetCompletion).min(1).max(UPLOADED_ASSET_MAX_FILES)
8317
8521
  }).strict()
8318
8522
  ]);
8319
- var UploadedAssetAdoptionRequest = z41.object({
8523
+ var UploadedAssetAdoptionRequest = z42.object({
8320
8524
  assetId: UploadedAssetId,
8321
- ownerId: z41.string().min(1),
8322
- consumer: z41.string().min(1).max(100),
8323
- idempotencyKey: z41.string().min(1).max(255)
8525
+ ownerId: z42.string().min(1),
8526
+ consumer: z42.string().min(1).max(100),
8527
+ idempotencyKey: z42.string().min(1).max(255)
8324
8528
  }).strict();
8325
8529
 
8326
8530
  // ../../packages/types/src/dropshipping-project.ts
8327
- import { z as z42 } from "zod";
8328
- var DropshippingProjectStatusSchema = z42.enum([
8531
+ import { z as z43 } from "zod";
8532
+ var DropshippingProjectStatusSchema = z43.enum([
8329
8533
  "active",
8330
8534
  "completed",
8331
8535
  "archived"
8332
8536
  ]);
8333
8537
  var DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN = "dropshipping-launch-v1";
8334
- var DropshippingRewardMilestoneSchema = z42.enum([
8538
+ var DropshippingRewardMilestoneSchema = z43.enum([
8335
8539
  "product_selected",
8336
8540
  "costing_saved",
8337
8541
  "product_media_ready",
@@ -8346,21 +8550,21 @@ var DROPSHIPPING_LAUNCH_REWARD_CREDITS = {
8346
8550
  campaign_created: 200
8347
8551
  };
8348
8552
  var DROPSHIPPING_LAUNCH_REWARD_TOTAL_CREDITS = Object.values(DROPSHIPPING_LAUNCH_REWARD_CREDITS).reduce((sum, credits) => sum + credits, 0);
8349
- var DropshippingProjectRewardMilestoneStateSchema = z42.object({
8553
+ var DropshippingProjectRewardMilestoneStateSchema = z43.object({
8350
8554
  milestone: DropshippingRewardMilestoneSchema,
8351
- credits: z42.number().int().positive(),
8352
- available: z42.boolean().optional(),
8353
- earned: z42.boolean(),
8354
- grantId: z42.string().uuid().nullable(),
8355
- grantedAt: z42.string().datetime().nullable()
8356
- });
8357
- var DropshippingProjectRewardProgressSchema = z42.object({
8358
- campaignKey: z42.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
8359
- totalCredits: z42.number().int().positive(),
8360
- earnedCredits: z42.number().int().nonnegative(),
8361
- milestones: z42.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
8362
- });
8363
- var DropshippingProjectPhaseSchema = z42.enum([
8555
+ credits: z43.number().int().positive(),
8556
+ available: z43.boolean().optional(),
8557
+ earned: z43.boolean(),
8558
+ grantId: z43.string().uuid().nullable(),
8559
+ grantedAt: z43.string().datetime().nullable()
8560
+ });
8561
+ var DropshippingProjectRewardProgressSchema = z43.object({
8562
+ campaignKey: z43.literal(DROPSHIPPING_LAUNCH_REWARD_CAMPAIGN),
8563
+ totalCredits: z43.number().int().positive(),
8564
+ earnedCredits: z43.number().int().nonnegative(),
8565
+ milestones: z43.array(DropshippingProjectRewardMilestoneStateSchema).length(DropshippingRewardMilestoneSchema.options.length)
8566
+ });
8567
+ var DropshippingProjectPhaseSchema = z43.enum([
8364
8568
  "research",
8365
8569
  "costing",
8366
8570
  "media",
@@ -8369,18 +8573,18 @@ var DropshippingProjectPhaseSchema = z42.enum([
8369
8573
  "advertising",
8370
8574
  "complete"
8371
8575
  ]);
8372
- var DropshippingProjectCostingInputSchema = z42.object({
8373
- country: z42.string().min(2).max(3),
8374
- supplierCost: z42.number().finite().positive(),
8375
- shippingCost: z42.number().finite().nonnegative(),
8376
- returnRate: z42.number().finite().min(0).lt(1),
8377
- cancellationRate: z42.number().finite().min(0).lt(1),
8378
- adsManagerCpa: z42.number().finite().nonnegative(),
8379
- desiredMargin: z42.number().finite().min(0).lt(1)
8576
+ var DropshippingProjectCostingInputSchema = z43.object({
8577
+ country: z43.string().min(2).max(3),
8578
+ supplierCost: z43.number().finite().positive(),
8579
+ shippingCost: z43.number().finite().nonnegative(),
8580
+ returnRate: z43.number().finite().min(0).lt(1),
8581
+ cancellationRate: z43.number().finite().min(0).lt(1),
8582
+ adsManagerCpa: z43.number().finite().nonnegative(),
8583
+ desiredMargin: z43.number().finite().min(0).lt(1)
8380
8584
  }).superRefine(({ returnRate, cancellationRate }, context) => {
8381
8585
  if (returnRate + cancellationRate >= 1) {
8382
8586
  context.addIssue({
8383
- code: z42.ZodIssueCode.custom,
8587
+ code: z43.ZodIssueCode.custom,
8384
8588
  path: [
8385
8589
  "cancellationRate"
8386
8590
  ],
@@ -8388,24 +8592,24 @@ var DropshippingProjectCostingInputSchema = z42.object({
8388
8592
  });
8389
8593
  }
8390
8594
  });
8391
- var DropshippingProjectCostingResultSchema = z42.object({
8392
- country: z42.string().min(2).max(3),
8393
- deliveryRate: z42.number().finite(),
8394
- adjustedShippingCost: z42.number().finite(),
8395
- adjustedCpa: z42.number().finite(),
8396
- totalCost: z42.number().finite(),
8397
- suggestedPrice: z42.number().finite(),
8398
- baseCommercialPrice: z42.number().finite(),
8399
- minimumSafePrice: z42.number().finite(),
8400
- commercialPrice: z42.number().finite(),
8401
- comparisonPrice: z42.number().finite(),
8402
- estimatedProfit: z42.number().finite(),
8403
- actualMargin: z42.number().finite(),
8404
- breakEvenAdsManagerCpa: z42.number().finite(),
8405
- wasPriceAutoAdjusted: z42.boolean(),
8406
- profitabilityStatus: z42.literal("healthy")
8407
- });
8408
- var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
8595
+ var DropshippingProjectCostingResultSchema = z43.object({
8596
+ country: z43.string().min(2).max(3),
8597
+ deliveryRate: z43.number().finite(),
8598
+ adjustedShippingCost: z43.number().finite(),
8599
+ adjustedCpa: z43.number().finite(),
8600
+ totalCost: z43.number().finite(),
8601
+ suggestedPrice: z43.number().finite(),
8602
+ baseCommercialPrice: z43.number().finite(),
8603
+ minimumSafePrice: z43.number().finite(),
8604
+ commercialPrice: z43.number().finite(),
8605
+ comparisonPrice: z43.number().finite(),
8606
+ estimatedProfit: z43.number().finite(),
8607
+ actualMargin: z43.number().finite(),
8608
+ breakEvenAdsManagerCpa: z43.number().finite(),
8609
+ wasPriceAutoAdjusted: z43.boolean(),
8610
+ profitabilityStatus: z43.literal("healthy")
8611
+ });
8612
+ var HttpUrlSchema = z43.string().url().max(2e3).refine((value) => {
8409
8613
  try {
8410
8614
  return [
8411
8615
  "http:",
@@ -8415,124 +8619,124 @@ var HttpUrlSchema = z42.string().url().max(2e3).refine((value) => {
8415
8619
  return false;
8416
8620
  }
8417
8621
  }, "Product URL must use HTTP(S)");
8418
- var ResearchCandidateOriginSchema = z42.object({
8419
- type: z42.literal("research_candidate"),
8420
- runId: z42.string().uuid(),
8421
- candidateSourceId: z42.string().min(1).max(160)
8422
- });
8423
- var TaskOriginSchema = z42.object({
8424
- type: z42.literal("task"),
8425
- taskId: z42.string().uuid()
8426
- });
8427
- var ManualOriginSchema = z42.object({
8428
- type: z42.literal("manual"),
8429
- clientRequestId: z42.string().uuid(),
8430
- country: z42.string().min(2).max(3).optional()
8431
- });
8432
- var CreateDropshippingProjectRequestSchema = z42.object({
8433
- origin: z42.discriminatedUnion("type", [
8622
+ var ResearchCandidateOriginSchema = z43.object({
8623
+ type: z43.literal("research_candidate"),
8624
+ runId: z43.string().uuid(),
8625
+ candidateSourceId: z43.string().min(1).max(160)
8626
+ });
8627
+ var TaskOriginSchema = z43.object({
8628
+ type: z43.literal("task"),
8629
+ taskId: z43.string().uuid()
8630
+ });
8631
+ var ManualOriginSchema = z43.object({
8632
+ type: z43.literal("manual"),
8633
+ clientRequestId: z43.string().uuid(),
8634
+ country: z43.string().min(2).max(3).optional()
8635
+ });
8636
+ var CreateDropshippingProjectRequestSchema = z43.object({
8637
+ origin: z43.discriminatedUnion("type", [
8434
8638
  ResearchCandidateOriginSchema,
8435
8639
  TaskOriginSchema,
8436
8640
  ManualOriginSchema
8437
8641
  ]),
8438
- productName: z42.string().trim().min(1).max(240).optional(),
8642
+ productName: z43.string().trim().min(1).max(240).optional(),
8439
8643
  productUrl: HttpUrlSchema.optional(),
8440
- country: z42.string().min(2).max(3).optional(),
8441
- languageCode: z42.string().min(2).max(16).optional()
8644
+ country: z43.string().min(2).max(3).optional(),
8645
+ languageCode: z43.string().min(2).max(16).optional()
8442
8646
  }).strict();
8443
- var SaveDropshippingProjectCostingRequestSchema = z42.object({
8647
+ var SaveDropshippingProjectCostingRequestSchema = z43.object({
8444
8648
  input: DropshippingProjectCostingInputSchema,
8445
- version: z42.number().int().positive()
8649
+ version: z43.number().int().positive()
8446
8650
  }).strict();
8447
- var DropshippingProjectNextActionSchema = z42.discriminatedUnion("kind", [
8448
- z42.object({
8449
- kind: z42.literal("cost_product"),
8450
- href: z42.string().min(1)
8651
+ var DropshippingProjectNextActionSchema = z43.discriminatedUnion("kind", [
8652
+ z43.object({
8653
+ kind: z43.literal("cost_product"),
8654
+ href: z43.string().min(1)
8451
8655
  }),
8452
- z42.object({
8453
- kind: z42.literal("provide_product_media"),
8454
- href: z42.string().min(1)
8656
+ z43.object({
8657
+ kind: z43.literal("provide_product_media"),
8658
+ href: z43.string().min(1)
8455
8659
  }),
8456
- z42.object({
8457
- kind: z42.literal("generate_creatives"),
8458
- href: z42.string().min(1)
8660
+ z43.object({
8661
+ kind: z43.literal("generate_creatives"),
8662
+ href: z43.string().min(1)
8459
8663
  }),
8460
- z42.object({
8461
- kind: z42.literal("wait_for_creatives"),
8462
- href: z42.string().min(1)
8664
+ z43.object({
8665
+ kind: z43.literal("wait_for_creatives"),
8666
+ href: z43.string().min(1)
8463
8667
  }),
8464
- z42.object({
8465
- kind: z42.literal("repair_creatives"),
8466
- href: z42.string().min(1)
8668
+ z43.object({
8669
+ kind: z43.literal("repair_creatives"),
8670
+ href: z43.string().min(1)
8467
8671
  }),
8468
- z42.object({
8469
- kind: z42.literal("complete_creative_set"),
8470
- href: z42.string().min(1)
8672
+ z43.object({
8673
+ kind: z43.literal("complete_creative_set"),
8674
+ href: z43.string().min(1)
8471
8675
  }),
8472
- z42.object({
8473
- kind: z42.literal("connect_shopify"),
8474
- href: z42.string().min(1)
8676
+ z43.object({
8677
+ kind: z43.literal("connect_shopify"),
8678
+ href: z43.string().min(1)
8475
8679
  }),
8476
- z42.object({
8477
- kind: z42.literal("publish_storefront"),
8478
- href: z42.string().min(1)
8680
+ z43.object({
8681
+ kind: z43.literal("publish_storefront"),
8682
+ href: z43.string().min(1)
8479
8683
  }),
8480
- z42.object({
8481
- kind: z42.literal("wait_for_storefront"),
8482
- href: z42.string().min(1)
8684
+ z43.object({
8685
+ kind: z43.literal("wait_for_storefront"),
8686
+ href: z43.string().min(1)
8483
8687
  }),
8484
- z42.object({
8485
- kind: z42.literal("connect_meta"),
8486
- href: z42.string().min(1)
8688
+ z43.object({
8689
+ kind: z43.literal("connect_meta"),
8690
+ href: z43.string().min(1)
8487
8691
  }),
8488
- z42.object({
8489
- kind: z42.literal("create_meta_campaign"),
8490
- href: z42.string().min(1)
8692
+ z43.object({
8693
+ kind: z43.literal("create_meta_campaign"),
8694
+ href: z43.string().min(1)
8491
8695
  }),
8492
- z42.object({
8493
- kind: z42.literal("repair_meta_campaign"),
8494
- href: z42.string().min(1)
8696
+ z43.object({
8697
+ kind: z43.literal("repair_meta_campaign"),
8698
+ href: z43.string().min(1)
8495
8699
  }),
8496
- z42.object({
8497
- kind: z42.literal("review_paused_campaign"),
8498
- href: z42.string().min(1)
8700
+ z43.object({
8701
+ kind: z43.literal("review_paused_campaign"),
8702
+ href: z43.string().min(1)
8499
8703
  })
8500
8704
  ]);
8501
- var DropshippingProjectResponseSchema = z42.object({
8502
- id: z42.string().uuid(),
8503
- taskId: z42.string().uuid().nullable(),
8705
+ var DropshippingProjectResponseSchema = z43.object({
8706
+ id: z43.string().uuid(),
8707
+ taskId: z43.string().uuid().nullable(),
8504
8708
  status: DropshippingProjectStatusSchema,
8505
- originKey: z42.string().min(1),
8506
- researchRunId: z42.string().uuid().nullable(),
8507
- candidateSourceId: z42.string().nullable(),
8508
- productName: z42.string().nullable(),
8709
+ originKey: z43.string().min(1),
8710
+ researchRunId: z43.string().uuid().nullable(),
8711
+ candidateSourceId: z43.string().nullable(),
8712
+ productName: z43.string().nullable(),
8509
8713
  productUrl: HttpUrlSchema.nullable(),
8510
- referenceImageUrl: z42.string().url().nullable(),
8511
- hasReferenceImage: z42.boolean(),
8512
- targetCountry: z42.string().nullable(),
8513
- languageCode: z42.string().nullable(),
8714
+ referenceImageUrl: z43.string().url().nullable(),
8715
+ hasReferenceImage: z43.boolean(),
8716
+ targetCountry: z43.string().nullable(),
8717
+ languageCode: z43.string().nullable(),
8514
8718
  costingInput: DropshippingProjectCostingInputSchema.nullable(),
8515
8719
  costingResult: DropshippingProjectCostingResultSchema.nullable(),
8516
- costingVersion: z42.number().int().positive().nullable(),
8517
- version: z42.number().int().positive(),
8720
+ costingVersion: z43.number().int().positive().nullable(),
8721
+ version: z43.number().int().positive(),
8518
8722
  phase: DropshippingProjectPhaseSchema,
8519
8723
  nextAction: DropshippingProjectNextActionSchema.nullable(),
8520
8724
  rewards: DropshippingProjectRewardProgressSchema,
8521
- createdAt: z42.string().datetime(),
8522
- updatedAt: z42.string().datetime()
8725
+ createdAt: z43.string().datetime(),
8726
+ updatedAt: z43.string().datetime()
8523
8727
  });
8524
8728
 
8525
8729
  // ../../packages/types/src/scene-subject-contract.ts
8526
- import { z as z43 } from "zod";
8527
- var SceneSubjectContractSchema = z43.object({
8528
- maxPeople: z43.number().int().min(1).max(10),
8529
- minPeople: z43.number().int().min(0).max(10),
8530
- minDistinctPeople: z43.number().int().min(0).max(10),
8531
- maxHands: z43.number().int().min(0).max(20)
8730
+ import { z as z44 } from "zod";
8731
+ var SceneSubjectContractSchema = z44.object({
8732
+ maxPeople: z44.number().int().min(1).max(10),
8733
+ minPeople: z44.number().int().min(0).max(10),
8734
+ minDistinctPeople: z44.number().int().min(0).max(10),
8735
+ maxHands: z44.number().int().min(0).max(20)
8532
8736
  }).superRefine((value, ctx) => {
8533
8737
  if (value.minPeople > value.maxPeople) {
8534
8738
  ctx.addIssue({
8535
- code: z43.ZodIssueCode.custom,
8739
+ code: z44.ZodIssueCode.custom,
8536
8740
  path: [
8537
8741
  "minPeople"
8538
8742
  ],
@@ -8541,7 +8745,7 @@ var SceneSubjectContractSchema = z43.object({
8541
8745
  }
8542
8746
  if (value.minDistinctPeople > value.maxPeople) {
8543
8747
  ctx.addIssue({
8544
- code: z43.ZodIssueCode.custom,
8748
+ code: z44.ZodIssueCode.custom,
8545
8749
  path: [
8546
8750
  "minDistinctPeople"
8547
8751
  ],
@@ -8550,7 +8754,7 @@ var SceneSubjectContractSchema = z43.object({
8550
8754
  }
8551
8755
  if (value.minDistinctPeople > value.minPeople) {
8552
8756
  ctx.addIssue({
8553
- code: z43.ZodIssueCode.custom,
8757
+ code: z44.ZodIssueCode.custom,
8554
8758
  path: [
8555
8759
  "minDistinctPeople"
8556
8760
  ],
@@ -8560,7 +8764,7 @@ var SceneSubjectContractSchema = z43.object({
8560
8764
  });
8561
8765
 
8562
8766
  // ../../packages/types/src/service-creative.ts
8563
- import { z as z44 } from "zod";
8767
+ import { z as z45 } from "zod";
8564
8768
  var SERVICE_CREATIVE_PUBLIC_PRESETS = [
8565
8769
  "auto",
8566
8770
  "ugc_dynamic",
@@ -8568,19 +8772,19 @@ var SERVICE_CREATIVE_PUBLIC_PRESETS = [
8568
8772
  "cinematic",
8569
8773
  "wild_card"
8570
8774
  ];
8571
- var ServiceCreativePresetSchema = z44.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
8775
+ var ServiceCreativePresetSchema = z45.enum(SERVICE_CREATIVE_PUBLIC_PRESETS);
8572
8776
  var SERVICE_REVIEW_MODES = [
8573
8777
  "automatic",
8574
8778
  "guided"
8575
8779
  ];
8576
- var ServiceReviewModeSchema = z44.enum(SERVICE_REVIEW_MODES);
8780
+ var ServiceReviewModeSchema = z45.enum(SERVICE_REVIEW_MODES);
8577
8781
  var SERVICE_HIGGSFIELD_MODES = [
8578
8782
  "product_review",
8579
8783
  "ugc_how_to",
8580
8784
  "tv_spot",
8581
8785
  "wild_card"
8582
8786
  ];
8583
- var ServiceHiggsfieldModeSchema = z44.enum(SERVICE_HIGGSFIELD_MODES);
8787
+ var ServiceHiggsfieldModeSchema = z45.enum(SERVICE_HIGGSFIELD_MODES);
8584
8788
  var SERVICE_MEDIA_ROLES = [
8585
8789
  "screen_demo",
8586
8790
  "ad_reference",
@@ -8588,20 +8792,20 @@ var SERVICE_MEDIA_ROLES = [
8588
8792
  "presenter_reference",
8589
8793
  "brand_asset"
8590
8794
  ];
8591
- var ServiceMediaRoleSchema = z44.enum(SERVICE_MEDIA_ROLES);
8592
- var ServiceMediaAssetSchema = z44.object({
8795
+ var ServiceMediaRoleSchema = z45.enum(SERVICE_MEDIA_ROLES);
8796
+ var ServiceMediaAssetSchema = z45.object({
8593
8797
  role: ServiceMediaRoleSchema,
8594
- s3Key: z44.string().min(1),
8798
+ s3Key: z45.string().min(1),
8595
8799
  /** HMAC ticket returned by the authenticated service-media presign route. */
8596
- uploadToken: z44.string().min(1).optional(),
8597
- mimeType: z44.string().min(1),
8598
- durationSec: z44.number().positive().max(30).optional(),
8599
- sourceName: z44.string().max(255).optional()
8800
+ uploadToken: z45.string().min(1).optional(),
8801
+ mimeType: z45.string().min(1),
8802
+ durationSec: z45.number().positive().max(30).optional(),
8803
+ sourceName: z45.string().max(255).optional()
8600
8804
  }).superRefine((asset, ctx) => {
8601
8805
  const isVideoRole = asset.role === "screen_demo" || asset.role === "ad_reference";
8602
8806
  if (isVideoRole && !asset.mimeType.startsWith("video/")) {
8603
8807
  ctx.addIssue({
8604
- code: z44.ZodIssueCode.custom,
8808
+ code: z45.ZodIssueCode.custom,
8605
8809
  message: `${asset.role} requires video media`,
8606
8810
  path: [
8607
8811
  "mimeType"
@@ -8610,7 +8814,7 @@ var ServiceMediaAssetSchema = z44.object({
8610
8814
  }
8611
8815
  if (isVideoRole && asset.durationSec === void 0) {
8612
8816
  ctx.addIssue({
8613
- code: z44.ZodIssueCode.custom,
8817
+ code: z45.ZodIssueCode.custom,
8614
8818
  message: `${asset.role} requires durationSec`,
8615
8819
  path: [
8616
8820
  "durationSec"
@@ -8619,7 +8823,7 @@ var ServiceMediaAssetSchema = z44.object({
8619
8823
  }
8620
8824
  if (!isVideoRole && !asset.mimeType.startsWith("image/")) {
8621
8825
  ctx.addIssue({
8622
- code: z44.ZodIssueCode.custom,
8826
+ code: z45.ZodIssueCode.custom,
8623
8827
  message: `${asset.role} requires image media`,
8624
8828
  path: [
8625
8829
  "mimeType"
@@ -8627,13 +8831,13 @@ var ServiceMediaAssetSchema = z44.object({
8627
8831
  });
8628
8832
  }
8629
8833
  });
8630
- var ServiceMediaBundleSchema = z44.object({
8631
- assets: z44.array(ServiceMediaAssetSchema).max(15).default([])
8834
+ var ServiceMediaBundleSchema = z45.object({
8835
+ assets: z45.array(ServiceMediaAssetSchema).max(15).default([])
8632
8836
  }).superRefine((bundle, ctx) => {
8633
8837
  const adReferences = bundle.assets.filter((asset) => asset.role === "ad_reference");
8634
8838
  if (adReferences.length > 5) {
8635
8839
  ctx.addIssue({
8636
- code: z44.ZodIssueCode.custom,
8840
+ code: z45.ZodIssueCode.custom,
8637
8841
  message: "A maximum of five ad_reference videos is allowed",
8638
8842
  path: [
8639
8843
  "assets"
@@ -8643,7 +8847,7 @@ var ServiceMediaBundleSchema = z44.object({
8643
8847
  const screenDemos = bundle.assets.filter((asset) => asset.role === "screen_demo");
8644
8848
  if (screenDemos.length > 3) {
8645
8849
  ctx.addIssue({
8646
- code: z44.ZodIssueCode.custom,
8850
+ code: z45.ZodIssueCode.custom,
8647
8851
  message: "A maximum of three screen_demo videos is allowed",
8648
8852
  path: [
8649
8853
  "assets"
@@ -8660,33 +8864,33 @@ var SERVICE_MEDIA_ANALYSIS_KINDS = [
8660
8864
  "product",
8661
8865
  "other"
8662
8866
  ];
8663
- var ServiceMediaAnalysisKindSchema = z44.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
8664
- var ServiceMediaAnalysisItemSchema = z44.object({
8665
- assetIndex: z44.number().int().nonnegative(),
8666
- mediaType: z44.enum([
8867
+ var ServiceMediaAnalysisKindSchema = z45.enum(SERVICE_MEDIA_ANALYSIS_KINDS);
8868
+ var ServiceMediaAnalysisItemSchema = z45.object({
8869
+ assetIndex: z45.number().int().nonnegative(),
8870
+ mediaType: z45.enum([
8667
8871
  "image",
8668
8872
  "video"
8669
8873
  ]),
8670
8874
  kind: ServiceMediaAnalysisKindSchema,
8671
- description: z44.string().min(1).max(1200),
8672
- tags: z44.array(z44.string().min(1).max(80)).max(12),
8673
- suggestedRoles: z44.array(ServiceMediaRoleSchema).max(4),
8674
- visiblePeople: z44.number().int().nonnegative().max(20).optional(),
8675
- ocrText: z44.string().max(2e3).optional(),
8676
- speechSummary: z44.string().max(2e3).optional()
8677
- });
8678
- var ServiceMediaAnalysisSchema = z44.object({
8679
- version: z44.literal(1),
8680
- analyzedAt: z44.string().datetime(),
8681
- pricingAction: z44.literal("service-media-analysis"),
8682
- assetFingerprint: z44.string().regex(/^[a-f0-9]{64}$/),
8875
+ description: z45.string().min(1).max(1200),
8876
+ tags: z45.array(z45.string().min(1).max(80)).max(12),
8877
+ suggestedRoles: z45.array(ServiceMediaRoleSchema).max(4),
8878
+ visiblePeople: z45.number().int().nonnegative().max(20).optional(),
8879
+ ocrText: z45.string().max(2e3).optional(),
8880
+ speechSummary: z45.string().max(2e3).optional()
8881
+ });
8882
+ var ServiceMediaAnalysisSchema = z45.object({
8883
+ version: z45.literal(1),
8884
+ analyzedAt: z45.string().datetime(),
8885
+ pricingAction: z45.literal("service-media-analysis"),
8886
+ assetFingerprint: z45.string().regex(/^[a-f0-9]{64}$/),
8683
8887
  /** Language used for human-readable descriptions and tags. Optional for old checkpoints. */
8684
- language: z44.enum([
8888
+ language: z45.enum([
8685
8889
  "es",
8686
8890
  "en",
8687
8891
  "pt"
8688
8892
  ]).optional(),
8689
- items: z44.array(ServiceMediaAnalysisItemSchema).max(15)
8893
+ items: z45.array(ServiceMediaAnalysisItemSchema).max(15)
8690
8894
  });
8691
8895
 
8692
8896
  // ../../packages/types/src/productmaker-capability-registry.ts
@@ -8744,6 +8948,7 @@ function slot(name, required, mcp2, whatsapp = mcp2, source = "user") {
8744
8948
  }
8745
8949
  __name(slot, "slot");
8746
8950
  var SERVER_SELECTED_OR_ARTIFACT_FIELDS = /* @__PURE__ */ new Set([
8951
+ "actionKind",
8747
8952
  "actorId",
8748
8953
  "adAccountId",
8749
8954
  "advertiserId",
@@ -9202,7 +9407,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9202
9407
  ], [
9203
9408
  "creative"
9204
9409
  ]),
9205
- defineCapability("generate_landing", "75947c5a70d26305ca07ab8d7bc0be710e862eb9319010ee66dd43e64fab2050", "credit_consuming", "creative", [
9410
+ defineCapability("generate_landing", "67520417176396516cd5ad358dca39f70a5c4a74169eecd85987217226c12251", "credit_consuming", "creative", [
9206
9411
  "credit_consuming",
9207
9412
  "state_change"
9208
9413
  ], [
@@ -9247,12 +9452,14 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9247
9452
  "competitorScanEnabled",
9248
9453
  "landingStrategy",
9249
9454
  "landingImageFormat",
9455
+ "pauseForAngleReview",
9250
9456
  // MCP-only, like create_product_task's aiDecidesAll.
9251
9457
  "aiDecidesAll"
9252
9458
  ], [
9253
9459
  "competitorScanEnabled",
9254
9460
  "landingStrategy",
9255
- "landingImageFormat"
9461
+ "landingImageFormat",
9462
+ "pauseForAngleReview"
9256
9463
  ])
9257
9464
  ], [
9258
9465
  "product_reference"
@@ -9568,7 +9775,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9568
9775
  ], [
9569
9776
  "meta_publication"
9570
9777
  ]),
9571
- defineCapability("publish_to_shopify", "ec37111aac872e6be22f34c8b547c1e4b58875f798c48e24ca9ca067a0a22d8b", "external_mutation", "publishing", [
9778
+ defineCapability("publish_to_shopify", "6a75ad9bfe04aed24ed41564011ac785657bab12caccd4d78c1de846ce798e82", "external_mutation", "publishing", [
9572
9779
  "external_mutation",
9573
9780
  "state_change"
9574
9781
  ], [
@@ -9588,7 +9795,13 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
9588
9795
  slot("publication_target", false, [
9589
9796
  "publicationMode",
9590
9797
  "targetProductId"
9591
- ])
9798
+ ]),
9799
+ // MCP + web only: the WhatsApp publish tool exposes no announcement fields,
9800
+ // and claiming them here breaks the schema-parity invariant.
9801
+ slot("announcement_bar", false, [
9802
+ "announcementBarEnabled",
9803
+ "announcementBarText"
9804
+ ], [])
9592
9805
  ], [
9593
9806
  "product",
9594
9807
  "shop"
@@ -9762,8 +9975,8 @@ var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_C
9762
9975
  var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
9763
9976
 
9764
9977
  // ../../packages/types/src/actor.ts
9765
- import { z as z45 } from "zod";
9766
- var ActorStatusSchema = z45.enum([
9978
+ import { z as z46 } from "zod";
9979
+ var ActorStatusSchema = z46.enum([
9767
9980
  "draft",
9768
9981
  "generating",
9769
9982
  "ready",
@@ -9772,18 +9985,18 @@ var ActorStatusSchema = z45.enum([
9772
9985
  var ACTOR_NAME_MAX_CHARS = 60;
9773
9986
  var ACTOR_DESCRIPTION_MAX_CHARS = 600;
9774
9987
  var ACTOR_CANDIDATE_COUNT = 4;
9775
- var ActorGenderSchema = z45.enum([
9988
+ var ActorGenderSchema = z46.enum([
9776
9989
  "female",
9777
9990
  "male",
9778
9991
  "non_binary"
9779
9992
  ]);
9780
- var ActorSchema = z45.object({
9781
- id: z45.string(),
9782
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS),
9783
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
9993
+ var ActorSchema = z46.object({
9994
+ id: z46.string(),
9995
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
9996
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS),
9784
9997
  gender: ActorGenderSchema,
9785
- voiceId: z45.string().nullable().optional(),
9786
- brandId: z45.string().nullable().optional(),
9998
+ voiceId: z46.string().nullable().optional(),
9999
+ brandId: z46.string().nullable().optional(),
9787
10000
  status: ActorStatusSchema,
9788
10001
  /**
9789
10002
  * A candidate-grid run is in flight RIGHT NOW.
@@ -9795,36 +10008,36 @@ var ActorSchema = z45.object({
9795
10008
  * timestamp with a TTL, so an orphaned run reads as `false` instead of
9796
10009
  * pinning the personaje forever.
9797
10010
  */
9798
- generating: z45.boolean().optional(),
9799
- photoUrl: z45.string().url().nullable().optional(),
9800
- avatarUrl: z45.string().url().nullable().optional(),
9801
- candidateUrls: z45.array(z45.string().url()).optional(),
10011
+ generating: z46.boolean().optional(),
10012
+ photoUrl: z46.string().url().nullable().optional(),
10013
+ avatarUrl: z46.string().url().nullable().optional(),
10014
+ candidateUrls: z46.array(z46.string().url()).optional(),
9802
10015
  /** Indices of candidateUrls that failed the anatomy check (Task 6). */
9803
- flaggedCandidateIndices: z45.array(z45.number().int()).optional()
10016
+ flaggedCandidateIndices: z46.array(z46.number().int()).optional()
9804
10017
  });
9805
- var CreateActorRequest = z45.object({
9806
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS),
9807
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
10018
+ var CreateActorRequest = z46.object({
10019
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS),
10020
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).default(""),
9808
10021
  gender: ActorGenderSchema,
9809
- voiceId: z45.string().optional(),
9810
- brandId: z45.string().optional(),
9811
- sourcePhotoKey: z45.string().optional()
10022
+ voiceId: z46.string().optional(),
10023
+ brandId: z46.string().optional(),
10024
+ sourcePhotoKey: z46.string().optional()
9812
10025
  }).refine((v) => v.description.trim().length > 0 || !!v.sourcePhotoKey, {
9813
10026
  message: "Se necesita una descripci\xF3n o una foto"
9814
10027
  });
9815
- var UpdateActorRequest = z45.object({
9816
- name: z45.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
9817
- description: z45.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
10028
+ var UpdateActorRequest = z46.object({
10029
+ name: z46.string().min(1).max(ACTOR_NAME_MAX_CHARS).optional(),
10030
+ description: z46.string().max(ACTOR_DESCRIPTION_MAX_CHARS).optional(),
9818
10031
  gender: ActorGenderSchema.optional(),
9819
- voiceId: z45.string().nullable().optional(),
9820
- brandId: z45.string().nullable().optional()
10032
+ voiceId: z46.string().nullable().optional(),
10033
+ brandId: z46.string().nullable().optional()
9821
10034
  });
9822
- var ApproveActorRequest = z45.object({
9823
- candidateIndex: z45.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
10035
+ var ApproveActorRequest = z46.object({
10036
+ candidateIndex: z46.number().int().min(0).max(ACTOR_CANDIDATE_COUNT - 1)
9824
10037
  });
9825
10038
 
9826
10039
  // ../../packages/types/src/referral-dashboard.ts
9827
- import { z as z46 } from "zod";
10040
+ import { z as z47 } from "zod";
9828
10041
  var REFERRAL_DASHBOARD_RAW_STATUSES = [
9829
10042
  "ACTIVE",
9830
10043
  "PENDING",
@@ -9843,66 +10056,66 @@ var REFERRAL_DASHBOARD_PLAN_KINDS = [
9843
10056
  "last",
9844
10057
  "none"
9845
10058
  ];
9846
- var ReferralDashboardRawStatusSchema = z46.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
9847
- var ReferralDashboardStateSchema = z46.enum(REFERRAL_DASHBOARD_STATES);
9848
- var ReferralDashboardPlanKindSchema = z46.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
9849
- var ReferralDashboardStatusSchema = z46.enum([
10059
+ var ReferralDashboardRawStatusSchema = z47.enum(REFERRAL_DASHBOARD_RAW_STATUSES);
10060
+ var ReferralDashboardStateSchema = z47.enum(REFERRAL_DASHBOARD_STATES);
10061
+ var ReferralDashboardPlanKindSchema = z47.enum(REFERRAL_DASHBOARD_PLAN_KINDS);
10062
+ var ReferralDashboardStatusSchema = z47.enum([
9850
10063
  "all",
9851
10064
  "active",
9852
10065
  "pending",
9853
10066
  "inactive"
9854
10067
  ]).default("all");
9855
- var ReferralDashboardPlanSchema = z46.string().trim().min(1).max(100).optional();
9856
- var ReferralDashboardFiltersSchema = z46.object({
10068
+ var ReferralDashboardPlanSchema = z47.string().trim().min(1).max(100).optional();
10069
+ var ReferralDashboardFiltersSchema = z47.object({
9857
10070
  status: ReferralDashboardStatusSchema,
9858
10071
  plan: ReferralDashboardPlanSchema
9859
10072
  });
9860
- var ReferralDashboardSubscriptionSchema = z46.object({
9861
- planName: z46.string().nullable(),
10073
+ var ReferralDashboardSubscriptionSchema = z47.object({
10074
+ planName: z47.string().nullable(),
9862
10075
  planKind: ReferralDashboardPlanKindSchema,
9863
10076
  rawStatus: ReferralDashboardRawStatusSchema.nullable(),
9864
10077
  state: ReferralDashboardStateSchema,
9865
- currentPeriodEnd: z46.string().datetime({
10078
+ currentPeriodEnd: z47.string().datetime({
9866
10079
  offset: true
9867
10080
  }).nullable(),
9868
- cancelAtPeriodEnd: z46.boolean()
10081
+ cancelAtPeriodEnd: z47.boolean()
9869
10082
  });
9870
- var ReferralDashboardReferralSchema = z46.object({
9871
- id: z46.string().min(1),
9872
- registeredAt: z46.string().datetime({
10083
+ var ReferralDashboardReferralSchema = z47.object({
10084
+ id: z47.string().min(1),
10085
+ registeredAt: z47.string().datetime({
9873
10086
  offset: true
9874
10087
  }),
9875
10088
  // Kept in the response for compatibility with existing referral API clients.
9876
10089
  // The referrer dashboard intentionally does not render these legacy fields.
9877
- expiresAt: z46.string().datetime({
10090
+ expiresAt: z47.string().datetime({
9878
10091
  offset: true
9879
10092
  }).nullable().optional(),
9880
- status: z46.string().min(1).optional(),
9881
- referredUser: z46.object({
9882
- email: z46.string().nullable()
10093
+ status: z47.string().min(1).optional(),
10094
+ referredUser: z47.object({
10095
+ email: z47.string().nullable()
9883
10096
  }),
9884
10097
  subscription: ReferralDashboardSubscriptionSchema
9885
10098
  });
9886
- var ReferralDashboardReferralsPageSchema = z46.object({
9887
- referrals: z46.array(ReferralDashboardReferralSchema),
9888
- total: z46.number().int().nonnegative()
10099
+ var ReferralDashboardReferralsPageSchema = z47.object({
10100
+ referrals: z47.array(ReferralDashboardReferralSchema),
10101
+ total: z47.number().int().nonnegative()
9889
10102
  });
9890
- var ReferralDashboardPlanBreakdownSchema = z46.object({
9891
- planName: z46.string().nullable(),
9892
- count: z46.number().int().nonnegative()
10103
+ var ReferralDashboardPlanBreakdownSchema = z47.object({
10104
+ planName: z47.string().nullable(),
10105
+ count: z47.number().int().nonnegative()
9893
10106
  });
9894
- var ReferralDashboardMonthlyRegistrationSchema = z46.object({
9895
- month: z46.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
9896
- count: z46.number().int().nonnegative()
10107
+ var ReferralDashboardMonthlyRegistrationSchema = z47.object({
10108
+ month: z47.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
10109
+ count: z47.number().int().nonnegative()
9897
10110
  });
9898
- var ReferralDashboardOverviewSchema = z46.object({
9899
- totalReferredUsers: z46.number().int().nonnegative(),
9900
- activeUsers: z46.number().int().nonnegative(),
9901
- pendingUsers: z46.number().int().nonnegative(),
9902
- inactiveUsers: z46.number().int().nonnegative(),
9903
- noPlanUsers: z46.number().int().nonnegative(),
9904
- planBreakdown: z46.array(ReferralDashboardPlanBreakdownSchema),
9905
- monthlyRegistrations: z46.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
10111
+ var ReferralDashboardOverviewSchema = z47.object({
10112
+ totalReferredUsers: z47.number().int().nonnegative(),
10113
+ activeUsers: z47.number().int().nonnegative(),
10114
+ pendingUsers: z47.number().int().nonnegative(),
10115
+ inactiveUsers: z47.number().int().nonnegative(),
10116
+ noPlanUsers: z47.number().int().nonnegative(),
10117
+ planBreakdown: z47.array(ReferralDashboardPlanBreakdownSchema),
10118
+ monthlyRegistrations: z47.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
9906
10119
  });
9907
10120
 
9908
10121
  // ../../packages/types/src/mcp-oauth.ts
@@ -10664,19 +10877,19 @@ import { InsufficientScopeError, InvalidTokenError } from "@modelcontextprotocol
10664
10877
  import { createRemoteJWKSet as createRemoteJWKSet2, jwtVerify as jwtVerify2 } from "jose";
10665
10878
 
10666
10879
  // src/auth/oauth-config.ts
10667
- import { z as z47 } from "zod";
10880
+ import { z as z48 } from "zod";
10668
10881
  var MCP_RESOURCE = new URL("https://mcp.productmaker.app/mcp");
10669
10882
  var MCP_SCOPE = "mcp:access";
10670
- var oauthEnvSchema = z47.object({
10671
- MCP_REMOTE_AUTH_MODE: z47.literal("oauth"),
10672
- MCP_AUTH0_ISSUER: z47.string().trim().min(1),
10673
- MCP_AUTH0_AUDIENCE: z47.string().trim().refine((value) => value === MCP_RESOURCE.href, {
10883
+ var oauthEnvSchema = z48.object({
10884
+ MCP_REMOTE_AUTH_MODE: z48.literal("oauth"),
10885
+ MCP_AUTH0_ISSUER: z48.string().trim().min(1),
10886
+ MCP_AUTH0_AUDIENCE: z48.string().trim().refine((value) => value === MCP_RESOURCE.href, {
10674
10887
  message: `must equal ${MCP_RESOURCE.href}`
10675
10888
  }),
10676
- MCP_AUTH0_CLIENT_ID: z47.string().trim().min(1),
10677
- MCP_AUTH0_CLIENT_SECRET: z47.string().trim().min(1),
10678
- PM_API_AUDIENCE: z47.string().trim().min(1),
10679
- AUTH0_AUDIENCE: z47.string().trim().min(1)
10889
+ MCP_AUTH0_CLIENT_ID: z48.string().trim().min(1),
10890
+ MCP_AUTH0_CLIENT_SECRET: z48.string().trim().min(1),
10891
+ PM_API_AUDIENCE: z48.string().trim().min(1),
10892
+ AUTH0_AUDIENCE: z48.string().trim().min(1)
10680
10893
  });
10681
10894
  function formatZodIssues(error) {
10682
10895
  return error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
@@ -11418,7 +11631,7 @@ var ApiClient = class {
11418
11631
  };
11419
11632
 
11420
11633
  // src/index.ts
11421
- var MCP_VERSION = "3.3.0";
11634
+ var MCP_VERSION = "3.4.1";
11422
11635
 
11423
11636
  // src/meta-upload-app.ts
11424
11637
  import { readFileSync } from "fs";
@@ -11490,7 +11703,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
11490
11703
  __name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
11491
11704
 
11492
11705
  // src/tools/tasks.ts
11493
- import { z as z49 } from "zod";
11706
+ import { z as z50 } from "zod";
11494
11707
 
11495
11708
  // src/resolve-image.ts
11496
11709
  import { lookup as dnsLookup } from "dns/promises";
@@ -12017,10 +12230,10 @@ async function resolveMediaRef(ref, opts) {
12017
12230
  __name(resolveMediaRef, "resolveMediaRef");
12018
12231
 
12019
12232
  // src/tools/shared.ts
12020
- import { z as z48 } from "zod";
12021
- var Lang = z48.string().min(2).max(10);
12022
- var Country = z48.string().min(2).max(3);
12023
- var LandingStrategyEnum = z48.enum([
12233
+ import { z as z49 } from "zod";
12234
+ var Lang = z49.string().min(2).max(10);
12235
+ var Country = z49.string().min(2).max(3);
12236
+ var LandingStrategyEnum = z49.enum([
12024
12237
  "auto",
12025
12238
  "classic",
12026
12239
  "pain_solution",
@@ -12029,12 +12242,12 @@ var LandingStrategyEnum = z48.enum([
12029
12242
  "offer_value",
12030
12243
  "lifestyle_identity"
12031
12244
  ]);
12032
- var LandingImageFormatEnum = z48.enum([
12245
+ var LandingImageFormatEnum = z49.enum([
12033
12246
  "9:16",
12034
12247
  "4:5",
12035
12248
  "1:1"
12036
12249
  ]);
12037
- var VideoPresetEnum = z48.enum([
12250
+ var VideoPresetEnum = z49.enum([
12038
12251
  "auto",
12039
12252
  "ugc_dynamic",
12040
12253
  "ugc_how_to",
@@ -12047,7 +12260,7 @@ var VideoPresetEnum = z48.enum([
12047
12260
  "pixar_problem",
12048
12261
  "pixar_sassy"
12049
12262
  ]);
12050
- var TaskCreativeVariantVideoPresetEnum = z48.enum([
12263
+ var TaskCreativeVariantVideoPresetEnum = z49.enum([
12051
12264
  "auto",
12052
12265
  "ugc_dynamic",
12053
12266
  "ugc_how_to",
@@ -12118,7 +12331,7 @@ function resolveVideoPreset(preset) {
12118
12331
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
12119
12332
  }
12120
12333
  __name(resolveVideoPreset, "resolveVideoPreset");
12121
- var VideoTierEnum = z48.enum([
12334
+ var VideoTierEnum = z49.enum([
12122
12335
  "basic"
12123
12336
  ]);
12124
12337
  function resolveVideoTier(_tier) {
@@ -12133,17 +12346,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
12133
12346
  return durationSeconds === 30 ? "double_30s" : "single_15s";
12134
12347
  }
12135
12348
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
12136
- var VideoResolutionEnum = z48.enum([
12349
+ var VideoResolutionEnum = z49.enum([
12137
12350
  "480p",
12138
12351
  "720p"
12139
12352
  ]);
12140
- var VideoStyleEnum = z48.enum([
12353
+ var VideoStyleEnum = z49.enum([
12141
12354
  "ugc",
12142
12355
  "cartoon_3d",
12143
12356
  "product_only",
12144
12357
  "ai_dynamic"
12145
12358
  ]);
12146
- var NarrativeStyleEnum = z48.enum([
12359
+ var NarrativeStyleEnum = z49.enum([
12147
12360
  "review",
12148
12361
  "ugc_how_to",
12149
12362
  "problem_product_cta",
@@ -12160,7 +12373,7 @@ var NarrativeStyleEnum = z48.enum([
12160
12373
  "ugc_skeptic_confession",
12161
12374
  "ugc_demo_review"
12162
12375
  ]);
12163
- var PublicNarrativeStyleEnum = z48.enum([
12376
+ var PublicNarrativeStyleEnum = z49.enum([
12164
12377
  "review",
12165
12378
  "ugc_how_to",
12166
12379
  "ugc_unboxing_chaos",
@@ -12172,7 +12385,7 @@ var PublicNarrativeStyleEnum = z48.enum([
12172
12385
  "animated_problem_pixar",
12173
12386
  "sassy_product_pixar"
12174
12387
  ]);
12175
- var VideoModelEnum = z48.enum([
12388
+ var VideoModelEnum = z49.enum([
12176
12389
  "veo",
12177
12390
  "wan",
12178
12391
  "bytedance",
@@ -12180,29 +12393,29 @@ var VideoModelEnum = z48.enum([
12180
12393
  "grok",
12181
12394
  "kling"
12182
12395
  ]);
12183
- var ProductReferenceUserChoiceEnum = z48.enum([
12396
+ var ProductReferenceUserChoiceEnum = z49.enum([
12184
12397
  "same_product",
12185
12398
  "variants",
12186
12399
  "different_products",
12187
12400
  "combo_distinct"
12188
12401
  ]);
12189
- var ProductReferenceCandidateGroupingEnum = z48.enum([
12402
+ var ProductReferenceCandidateGroupingEnum = z49.enum([
12190
12403
  "same_product_same_variant",
12191
12404
  "same_product_different_variants",
12192
12405
  "different_products",
12193
12406
  "unclear"
12194
12407
  ]);
12195
- var ProductReferenceScopeEnum = z48.enum([
12408
+ var ProductReferenceScopeEnum = z49.enum([
12196
12409
  "base",
12197
12410
  "video_view_pack"
12198
12411
  ]);
12199
- var ProductReferenceResolutionDecisionEnum = z48.enum([
12412
+ var ProductReferenceResolutionDecisionEnum = z49.enum([
12200
12413
  "approve",
12201
12414
  "reject",
12202
12415
  "observed_only",
12203
12416
  "correct_view"
12204
12417
  ]);
12205
- var ProductReferenceRejectionReasonEnum = z48.enum([
12418
+ var ProductReferenceRejectionReasonEnum = z49.enum([
12206
12419
  "replacement_requested",
12207
12420
  "wrong_product_identity",
12208
12421
  "wrong_variant",
@@ -12213,13 +12426,13 @@ var ProductReferenceRejectionReasonEnum = z48.enum([
12213
12426
  "not_a_product_photo",
12214
12427
  "other"
12215
12428
  ]);
12216
- var GapDurationChoiceMirror = z48.object({
12217
- angleIndex: z48.number().int().nonnegative(),
12218
- recommendedSec: z48.union([
12219
- z48.literal(15),
12220
- z48.literal(30)
12429
+ var GapDurationChoiceMirror = z49.object({
12430
+ angleIndex: z49.number().int().nonnegative(),
12431
+ recommendedSec: z49.union([
12432
+ z49.literal(15),
12433
+ z49.literal(30)
12221
12434
  ]),
12222
- estimatedDeltaCredits: z48.number().int().nonnegative().optional()
12435
+ estimatedDeltaCredits: z49.number().int().nonnegative().optional()
12223
12436
  });
12224
12437
  function buildSelectedImageUrlsHash(urls) {
12225
12438
  const input = [
@@ -12229,13 +12442,13 @@ function buildSelectedImageUrlsHash(urls) {
12229
12442
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
12230
12443
  }
12231
12444
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
12232
- var ImageCreativeStyleEnum = z48.enum([
12445
+ var ImageCreativeStyleEnum = z49.enum([
12233
12446
  "studio",
12234
12447
  "floating",
12235
12448
  "ingredients",
12236
12449
  "in_use"
12237
12450
  ]);
12238
- var ImageCreativeArchetypeEnum = z48.enum([
12451
+ var ImageCreativeArchetypeEnum = z49.enum([
12239
12452
  "direct_response_product_ad",
12240
12453
  "hero_outcome",
12241
12454
  "offer_value_prop",
@@ -12244,20 +12457,20 @@ var ImageCreativeArchetypeEnum = z48.enum([
12244
12457
  "problem_solution",
12245
12458
  "lifestyle_identity"
12246
12459
  ]);
12247
- var ImageCreativeVisualTreatmentEnum = z48.enum([
12460
+ var ImageCreativeVisualTreatmentEnum = z49.enum([
12248
12461
  "performance_product_ad",
12249
12462
  "premium_dtc_carousel"
12250
12463
  ]);
12251
- var ImageCreativeAspectRatioEnum = z48.enum([
12464
+ var ImageCreativeAspectRatioEnum = z49.enum([
12252
12465
  "1:1",
12253
12466
  "4:5",
12254
12467
  "9:16",
12255
12468
  "16:9"
12256
12469
  ]);
12257
- var ImageRefSchema = z48.object({
12258
- 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.'),
12259
- 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`.'),
12260
- 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.")
12470
+ var ImageRefSchema = z49.object({
12471
+ url: z49.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.'),
12472
+ path: z49.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`.'),
12473
+ assetId: z49.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.")
12261
12474
  }).refine((v) => [
12262
12475
  v.url,
12263
12476
  v.path,
@@ -12265,11 +12478,11 @@ var ImageRefSchema = z48.object({
12265
12478
  ].filter(Boolean).length === 1, {
12266
12479
  message: "each image entry must have exactly one of `url`, `path`, or `assetId`"
12267
12480
  });
12268
- var ImagesInput = z48.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
12481
+ var ImagesInput = z49.array(ImageRefSchema).min(1).max(5).superRefine((images, ctx) => {
12269
12482
  const assetCount = images.filter((image) => Boolean(image.assetId)).length;
12270
12483
  if (assetCount > 0 && assetCount !== images.length) {
12271
12484
  ctx.addIssue({
12272
- code: z48.ZodIssueCode.custom,
12485
+ code: z49.ZodIssueCode.custom,
12273
12486
  message: "all image entries must use assetId, or all must use URL/path sources"
12274
12487
  });
12275
12488
  }
@@ -12339,14 +12552,14 @@ async function submitCreative(api, opts) {
12339
12552
  });
12340
12553
  }
12341
12554
  __name(submitCreative, "submitCreative");
12342
- var ResearchTargetCountryEnum = z48.enum([
12555
+ var ResearchTargetCountryEnum = z49.enum([
12343
12556
  "CO",
12344
12557
  "MX",
12345
12558
  "PE",
12346
12559
  "EC",
12347
12560
  "CL"
12348
12561
  ]);
12349
- var ResearchCategoryEnum = z48.enum([
12562
+ var ResearchCategoryEnum = z49.enum([
12350
12563
  "auto",
12351
12564
  "automotive",
12352
12565
  "kitchen",
@@ -12361,6 +12574,42 @@ var ResearchCategoryEnum = z48.enum([
12361
12574
  "garden",
12362
12575
  "cleaning"
12363
12576
  ]);
12577
+ var READS_INTERNAL = {
12578
+ readOnlyHint: true,
12579
+ destructiveHint: false,
12580
+ idempotentHint: true,
12581
+ openWorldHint: false
12582
+ };
12583
+ var READS_EXTERNAL = {
12584
+ readOnlyHint: true,
12585
+ destructiveHint: false,
12586
+ idempotentHint: true,
12587
+ openWorldHint: true
12588
+ };
12589
+ var CREATES_INTERNAL = {
12590
+ readOnlyHint: false,
12591
+ destructiveHint: false,
12592
+ idempotentHint: false,
12593
+ openWorldHint: false
12594
+ };
12595
+ var CREATES_EXTERNAL = {
12596
+ readOnlyHint: false,
12597
+ destructiveHint: false,
12598
+ idempotentHint: false,
12599
+ openWorldHint: true
12600
+ };
12601
+ var OVERWRITES_INTERNAL = {
12602
+ readOnlyHint: false,
12603
+ destructiveHint: true,
12604
+ idempotentHint: false,
12605
+ openWorldHint: false
12606
+ };
12607
+ var OVERWRITES_EXTERNAL = {
12608
+ readOnlyHint: false,
12609
+ destructiveHint: true,
12610
+ idempotentHint: false,
12611
+ openWorldHint: true
12612
+ };
12364
12613
 
12365
12614
  // src/errors.ts
12366
12615
  var McpErrorCode = {
@@ -12818,6 +13067,18 @@ function translateError(err) {
12818
13067
  }
12819
13068
  __name(translateError, "translateError");
12820
13069
 
13070
+ // src/tools/approve-refusal-message.ts
13071
+ function approveRefusalMessage(reason) {
13072
+ if (reason === "photo_review_pending") {
13073
+ 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.";
13074
+ }
13075
+ if (reason === "photos_not_ready") {
13076
+ 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.";
13077
+ }
13078
+ return "No se inici\xF3 la generaci\xF3n y no se cobr\xF3 nada. Los cambios siguen guardados: vuelve a intentar.";
13079
+ }
13080
+ __name(approveRefusalMessage, "approveRefusalMessage");
13081
+
12821
13082
  // src/tools/task-outputs.ts
12822
13083
  var WEBAPP_BASE = "https://productmaker.app";
12823
13084
  function proxifyOutputs(outputs, proxyBaseUrl) {
@@ -12946,54 +13207,54 @@ function renderOutputsMarkdown(outputs) {
12946
13207
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
12947
13208
 
12948
13209
  // src/tools/tasks.ts
12949
- var CreateProductTaskInput = z49.object({
13210
+ var CreateProductTaskInput = z50.object({
12950
13211
  images: ImagesInput.optional().describe("1\u20135 fotos del mismo producto. Omitir \xFAnicamente cuando projectId apunta a un proyecto propio con imagen guardada."),
12951
- 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."),
12952
- 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."),
13212
+ projectId: z50.string().uuid().optional().describe("Opcional. Proyecto guiado propio. Permite continuar sin volver a subir la imagen guardada; nunca evita cr\xE9ditos, cuotas ni confirmaciones."),
13213
+ productUrl: z50.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."),
12953
13214
  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.'),
12954
13215
  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.'),
12955
- 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.'),
12956
- 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".'),
12957
- 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.'),
12958
- 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.'),
12959
- 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."),
12960
- 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.'),
13216
+ price: z50.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.'),
13217
+ compareAtPrice: z50.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".'),
13218
+ offer: z50.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.'),
13219
+ paymentMethods: z50.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.'),
13220
+ 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."),
13221
+ productLabel: z50.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.'),
12961
13222
  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.'),
12962
- durationSeconds: z49.union([
12963
- z49.literal(15),
12964
- z49.literal(30)
13223
+ durationSeconds: z50.union([
13224
+ z50.literal(15),
13225
+ z50.literal(30)
12965
13226
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
12966
- 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."),
12967
- 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."),
13227
+ 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. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
13228
+ 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, 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."),
12968
13229
  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.'),
12969
13230
  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).'),
12970
- 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."),
13231
+ multiAngleVideos: z50.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."),
12971
13232
  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.'),
12972
- outputs: z49.array(z49.enum([
13233
+ outputs: z50.array(z50.enum([
12973
13234
  "landing",
12974
13235
  "video",
12975
13236
  "image"
12976
13237
  ])).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.'),
12977
- 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."),
12978
- 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."),
12979
- 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."),
12980
- 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.'),
12981
- 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."),
12982
- 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."),
13238
+ 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."),
13239
+ 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 los outputs generados."),
13240
+ actorId: z50.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."),
13241
+ creativeDirection: z50.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.'),
13242
+ headline: z50.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."),
13243
+ 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."),
12983
13244
  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".'),
12984
13245
  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".')
12985
13246
  }).strict();
12986
- var CreateServiceVideoInput = z49.object({
12987
- 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."),
12988
- 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.'),
13247
+ var CreateServiceVideoInput = z50.object({
13248
+ landingUrl: z50.string().url().describe("URL p\xFAblica de la landing del servicio. Es suficiente para generar el video; los medios son opcionales en la web."),
13249
+ additionalContext: z50.string().max(3e3).optional().describe('Instrucci\xF3n natural prioritaria. Ej.: "adapta el ritmo de estos competidores", "enfatiza reservas de demos" o restricciones de claims.'),
12989
13250
  language: Lang.describe("Idioma del video: es, en o pt."),
12990
- 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.'),
12991
- brandId: z49.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
12992
- reviewMode: z49.enum([
13251
+ voiceId: z50.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.'),
13252
+ brandId: z50.string().uuid().optional().describe("Marca guardada del usuario. Aplica voz, mensajes, paleta y persona predeterminada."),
13253
+ reviewMode: z50.enum([
12993
13254
  "automatic",
12994
13255
  "guided"
12995
13256
  ]).default("automatic").describe("automatic = la IA elige concepto y guion sin pausas; guided = el usuario aprueba concepto y storyboard."),
12996
- videoPreset: z49.enum([
13257
+ videoPreset: z50.enum([
12997
13258
  "auto",
12998
13259
  "ugc_dynamic",
12999
13260
  "ugc_how_to",
@@ -13001,51 +13262,51 @@ var CreateServiceVideoInput = z49.object({
13001
13262
  "wild_card"
13002
13263
  ]).default("auto")
13003
13264
  }).strict();
13004
- var GetTaskStatusInput = z49.object({
13005
- taskId: z49.string().min(1),
13006
- waitSeconds: z49.number().int().min(0).max(120).optional()
13007
- });
13008
- var ListTasksInput = z49.object({
13009
- limit: z49.number().int().min(1).max(50).optional(),
13010
- offset: z49.number().int().min(0).optional(),
13011
- search: z49.string().optional()
13012
- });
13013
- var EditTaskDraftInput = z49.object({
13014
- taskId: z49.string().min(1),
13015
- angleIndex: z49.number().int().min(0),
13016
- angle: z49.object({
13017
- angle: z49.string().min(1).max(480),
13018
- buyerPersona: z49.string().min(1).max(360),
13019
- actor: z49.string().min(1).max(360).optional(),
13020
- 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."),
13021
- gender: z49.enum([
13265
+ var GetTaskStatusInput = z50.object({
13266
+ taskId: z50.string().min(1),
13267
+ waitSeconds: z50.number().int().min(0).max(120).optional()
13268
+ });
13269
+ var ListTasksInput = z50.object({
13270
+ limit: z50.number().int().min(1).max(50).optional(),
13271
+ offset: z50.number().int().min(0).optional(),
13272
+ search: z50.string().optional()
13273
+ });
13274
+ var EditTaskDraftInput = z50.object({
13275
+ taskId: z50.string().min(1),
13276
+ angleIndex: z50.number().int().min(0),
13277
+ angle: z50.object({
13278
+ angle: z50.string().min(1).max(480),
13279
+ buyerPersona: z50.string().min(1).max(360),
13280
+ actor: z50.string().min(1).max(360).optional(),
13281
+ actorId: z50.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."),
13282
+ gender: z50.enum([
13022
13283
  "female",
13023
13284
  "male"
13024
13285
  ]).optional(),
13025
- scriptNotes: z49.string().max(2e3).optional()
13286
+ scriptNotes: z50.string().max(2e3).optional()
13026
13287
  })
13027
13288
  });
13028
- var PendingActionPayload = z49.discriminatedUnion("kind", [
13029
- z49.object({
13030
- kind: z49.literal("product_reference_set_approval"),
13289
+ var PendingActionPayload = z50.discriminatedUnion("kind", [
13290
+ z50.object({
13291
+ kind: z50.literal("product_reference_set_approval"),
13031
13292
  scope: ProductReferenceScopeEnum,
13032
- scopeKey: z49.string().min(1).max(120),
13033
- scopeSnapshotId: z49.string().uuid(),
13034
- expectedScopeHash: z49.string().min(1).max(128),
13293
+ scopeKey: z50.string().min(1).max(120),
13294
+ scopeSnapshotId: z50.string().uuid(),
13295
+ expectedScopeHash: z50.string().min(1).max(128),
13035
13296
  decision: ProductReferenceResolutionDecisionEnum,
13036
13297
  reason: ProductReferenceRejectionReasonEnum.optional(),
13037
- affectedRefKeys: z49.array(z49.string().min(1).max(200)).min(1).optional(),
13038
- recoveryAction: z49.enum([
13298
+ affectedRefKeys: z50.array(z50.string().min(1).max(200)).min(1).optional(),
13299
+ recoveryAction: z50.enum([
13039
13300
  "remove",
13040
13301
  "replace",
13041
13302
  "upload",
13042
13303
  "return_to_selection"
13043
13304
  ]).optional()
13044
13305
  }),
13045
- z49.object({
13046
- kind: z49.literal("product_reference_confirmation"),
13306
+ z50.object({
13307
+ kind: z50.literal("product_reference_confirmation"),
13047
13308
  userChoice: ProductReferenceUserChoiceEnum.optional(),
13048
- confirmedProductNames: z49.array(z49.string().min(1).max(120)).min(2).max(8).optional(),
13309
+ confirmedProductNames: z50.array(z50.string().min(1).max(120)).min(2).max(8).optional(),
13049
13310
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
13050
13311
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
13051
13312
  // product in several colors): renders one shot per color and stitches them,
@@ -13056,50 +13317,50 @@ var PendingActionPayload = z49.discriminatedUnion("kind", [
13056
13317
  // safe because multi-scene IS the intended render for a color variant set,
13057
13318
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
13058
13319
  // confirm call; the backend re-gates it (variants + variant_set only).
13059
- 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".')
13320
+ useMultiSceneShowcase: z50.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".')
13060
13321
  }),
13061
- z49.object({
13062
- kind: z49.literal("angle_review_approval"),
13322
+ z50.object({
13323
+ kind: z50.literal("angle_review_approval"),
13063
13324
  // All edits are optional — empty action body just approves the
13064
13325
  // backend-generated draft as-is. Field shapes mirror the angle-review
13065
13326
  // request contracts in @packages/types/wizard.ts; the handler routes to
13066
13327
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
13067
- customAngle: z49.string().min(1).max(480).optional(),
13328
+ customAngle: z50.string().min(1).max(480).optional(),
13068
13329
  // Keep the shared MCP action within the strictest downstream contract:
13069
13330
  // autopilot/image resume endpoints cap these fields at 1500 characters.
13070
- painPoints: z49.string().max(1500).optional(),
13071
- benefits: z49.string().max(1500).optional(),
13072
- features: z49.array(z49.string().min(1).max(300)).max(10).optional(),
13073
- gapDurationSeconds: z49.union([
13074
- z49.literal(15),
13075
- z49.literal(30)
13331
+ painPoints: z50.string().max(1500).optional(),
13332
+ benefits: z50.string().max(1500).optional(),
13333
+ features: z50.array(z50.string().min(1).max(300)).max(10).optional(),
13334
+ gapDurationSeconds: z50.union([
13335
+ z50.literal(15),
13336
+ z50.literal(30)
13076
13337
  ]).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.")
13077
13338
  }),
13078
- z49.object({
13079
- kind: z49.literal("service_angle_approval"),
13080
- angleId: z49.string().uuid(),
13081
- content: z49.string().min(1).max(480).optional(),
13082
- creativeDirection: z49.string().min(1).max(1500).optional(),
13083
- actor: z49.string().min(1).max(360).optional()
13339
+ z50.object({
13340
+ kind: z50.literal("service_angle_approval"),
13341
+ angleId: z50.string().uuid(),
13342
+ content: z50.string().min(1).max(480).optional(),
13343
+ creativeDirection: z50.string().min(1).max(1500).optional(),
13344
+ actor: z50.string().min(1).max(360).optional()
13084
13345
  }),
13085
- z49.object({
13086
- kind: z49.literal("service_script_approval"),
13087
- voiceovers: z49.record(z49.string(), z49.string().min(1).max(600)).optional(),
13088
- visualDirections: z49.record(z49.string(), z49.string().min(1).max(1200)).optional()
13346
+ z50.object({
13347
+ kind: z50.literal("service_script_approval"),
13348
+ voiceovers: z50.record(z50.string(), z50.string().min(1).max(600)).optional(),
13349
+ visualDirections: z50.record(z50.string(), z50.string().min(1).max(1200)).optional()
13089
13350
  })
13090
13351
  ]);
13091
- var ResolvePendingActionInput = z49.object({
13092
- taskId: z49.string().min(1),
13352
+ var ResolvePendingActionInput = z50.object({
13353
+ taskId: z50.string().min(1),
13093
13354
  action: PendingActionPayload
13094
13355
  }).strict();
13095
13356
  function resolveVariantConfirmVideoTier(action) {
13096
13357
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
13097
13358
  }
13098
13359
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
13099
- var StatusForResolveSchema = z49.object({
13360
+ var StatusForResolveSchema = z50.object({
13100
13361
  // Metadata used to select the backend resume contract. Older API responses
13101
13362
  // may omit these fields, so the resolver keeps the generic fallback.
13102
- kind: z49.enum([
13363
+ kind: z50.enum([
13103
13364
  "autopilot",
13104
13365
  "video",
13105
13366
  "image",
@@ -13107,24 +13368,24 @@ var StatusForResolveSchema = z49.object({
13107
13368
  "mix",
13108
13369
  "service"
13109
13370
  ]).optional(),
13110
- selectedOutputs: z49.array(z49.enum([
13371
+ selectedOutputs: z50.array(z50.enum([
13111
13372
  "landing",
13112
13373
  "video",
13113
13374
  "image"
13114
13375
  ])).optional(),
13115
- videoAngleCount: z49.number().int().min(1).max(3).optional(),
13116
- ugcFormat: z49.enum([
13376
+ videoAngleCount: z50.number().int().min(1).max(3).optional(),
13377
+ ugcFormat: z50.enum([
13117
13378
  "single_8s",
13118
13379
  "single_15s",
13119
13380
  "double_30s"
13120
13381
  ]).optional(),
13121
- videoStyle: z49.enum([
13382
+ videoStyle: z50.enum([
13122
13383
  "ugc",
13123
13384
  "cartoon_3d",
13124
13385
  "product_only",
13125
13386
  "ai_dynamic"
13126
13387
  ]).optional(),
13127
- videoModel: z49.enum([
13388
+ videoModel: z50.enum([
13128
13389
  "veo",
13129
13390
  "wan",
13130
13391
  "bytedance",
@@ -13132,62 +13393,62 @@ var StatusForResolveSchema = z49.object({
13132
13393
  "grok",
13133
13394
  "kling"
13134
13395
  ]).optional(),
13135
- videoTier: z49.enum([
13396
+ videoTier: z50.enum([
13136
13397
  "legacy",
13137
13398
  "standard",
13138
13399
  "pro",
13139
13400
  "single_call"
13140
13401
  ]).optional(),
13141
- videoResolution: z49.enum([
13402
+ videoResolution: z50.enum([
13142
13403
  "480p",
13143
13404
  "720p"
13144
13405
  ]).optional(),
13145
13406
  narrativeStyle: NarrativeStyleEnum.optional(),
13146
- pendingUserAction: z49.object({
13147
- kind: z49.string(),
13148
- candidateGrouping: z49.string().optional(),
13149
- selectedImageUrls: z49.array(z49.string()).optional(),
13150
- proposedProductNames: z49.array(z49.string()).optional(),
13407
+ pendingUserAction: z50.object({
13408
+ kind: z50.string(),
13409
+ candidateGrouping: z50.string().optional(),
13410
+ selectedImageUrls: z50.array(z50.string()).optional(),
13411
+ proposedProductNames: z50.array(z50.string()).optional(),
13151
13412
  // Adaptive reference questions are server-owned. Echo only the evidence
13152
13413
  // identity and the choices shown by the current snapshot; never accept a
13153
13414
  // client-supplied reference set as authorization.
13154
- options: z49.array(ProductReferenceUserChoiceEnum).max(4).optional(),
13155
- mode: z49.enum([
13415
+ options: z50.array(ProductReferenceUserChoiceEnum).max(4).optional(),
13416
+ mode: z50.enum([
13156
13417
  "question",
13157
13418
  "visual_recovery"
13158
13419
  ]).optional(),
13159
- decisionKey: z49.string().optional(),
13160
- questionOrdinal: z49.number().int().min(1).max(5).optional(),
13161
- questionCeiling: z49.union([
13162
- z49.literal(3),
13163
- z49.literal(5)
13420
+ decisionKey: z50.string().optional(),
13421
+ questionOrdinal: z50.number().int().min(1).max(5).optional(),
13422
+ questionCeiling: z50.union([
13423
+ z50.literal(3),
13424
+ z50.literal(5)
13164
13425
  ]).optional(),
13165
- referenceEvidenceVersion: z49.number().int().positive().optional(),
13166
- expectedEvidenceHash: z49.string().optional(),
13167
- question: z49.object({
13168
- prompt: z49.string().min(1).max(240),
13169
- helpText: z49.string().min(1).max(300).optional(),
13170
- options: z49.array(z49.object({
13426
+ referenceEvidenceVersion: z50.number().int().positive().optional(),
13427
+ expectedEvidenceHash: z50.string().optional(),
13428
+ question: z50.object({
13429
+ prompt: z50.string().min(1).max(240),
13430
+ helpText: z50.string().min(1).max(300).optional(),
13431
+ options: z50.array(z50.object({
13171
13432
  value: ProductReferenceUserChoiceEnum,
13172
- label: z49.string().min(1).max(120),
13173
- description: z49.string().min(1).max(240).optional()
13433
+ label: z50.string().min(1).max(120),
13434
+ description: z50.string().min(1).max(240).optional()
13174
13435
  })).min(2).max(4)
13175
13436
  }).optional(),
13176
13437
  scope: ProductReferenceScopeEnum.optional(),
13177
- scopeKey: z49.string().optional(),
13178
- scopeSnapshotId: z49.string().uuid().optional(),
13179
- scopeHash: z49.string().optional(),
13180
- baseReferenceSetHash: z49.string().optional(),
13181
- phase: z49.enum([
13438
+ scopeKey: z50.string().optional(),
13439
+ scopeSnapshotId: z50.string().uuid().optional(),
13440
+ scopeHash: z50.string().optional(),
13441
+ baseReferenceSetHash: z50.string().optional(),
13442
+ phase: z50.enum([
13182
13443
  "preparation",
13183
13444
  "final"
13184
13445
  ]).optional(),
13185
- selectedRefKeys: z49.array(z49.string().min(1).max(200)).optional(),
13186
- allowedDecisions: z49.array(ProductReferenceResolutionDecisionEnum).optional(),
13187
- customAngleDraft: z49.string().optional(),
13188
- painPointsDraft: z49.string().optional(),
13189
- benefitsDraft: z49.string().optional(),
13190
- featuresDraft: z49.array(z49.string()).optional(),
13446
+ selectedRefKeys: z50.array(z50.string().min(1).max(200)).optional(),
13447
+ allowedDecisions: z50.array(ProductReferenceResolutionDecisionEnum).optional(),
13448
+ customAngleDraft: z50.string().optional(),
13449
+ painPointsDraft: z50.string().optional(),
13450
+ benefitsDraft: z50.string().optional(),
13451
+ featuresDraft: z50.array(z50.string()).optional(),
13191
13452
  // Recommendation consumed only when the caller explicitly sends
13192
13453
  // gapDurationSeconds: 30 in the angle approval action.
13193
13454
  gapDurationChoice: GapDurationChoiceMirror.optional(),
@@ -13196,17 +13457,17 @@ var StatusForResolveSchema = z49.object({
13196
13457
  // keys. `actorId` (the saved custom actor the picker stored) was silently
13197
13458
  // dropped here, so the approval handler below could not echo it back on the
13198
13459
  // PATCH — the selection looked accepted and reached no render.
13199
- angles: z49.array(z49.object({
13200
- id: z49.string().uuid(),
13201
- content: z49.string(),
13202
- creativeDirection: z49.string(),
13203
- actor: z49.string(),
13204
- actorId: z49.string().nullable().optional()
13460
+ angles: z50.array(z50.object({
13461
+ id: z50.string().uuid(),
13462
+ content: z50.string(),
13463
+ creativeDirection: z50.string(),
13464
+ actor: z50.string(),
13465
+ actorId: z50.string().nullable().optional()
13205
13466
  }).passthrough()).optional(),
13206
- panels: z49.array(z49.object({
13207
- beatId: z49.string(),
13208
- voiceover: z49.string(),
13209
- visualDirection: z49.string()
13467
+ panels: z50.array(z50.object({
13468
+ beatId: z50.string(),
13469
+ voiceover: z50.string(),
13470
+ visualDirection: z50.string()
13210
13471
  }).passthrough()).optional()
13211
13472
  }).optional()
13212
13473
  }).passthrough();
@@ -13245,7 +13506,7 @@ function resolveStatusTaskKind(status) {
13245
13506
  }
13246
13507
  __name(resolveStatusTaskKind, "resolveStatusTaskKind");
13247
13508
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
13248
- 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) => {
13509
+ 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) => {
13249
13510
  try {
13250
13511
  if (!input.images && !input.projectId) {
13251
13512
  throw new Error("images es requerido salvo que env\xEDes projectId.");
@@ -13313,7 +13574,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13313
13574
  throw translateError(e);
13314
13575
  }
13315
13576
  });
13316
- 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) => {
13577
+ 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) => {
13317
13578
  try {
13318
13579
  const created = await api.postJson("/v2/ingest", {
13319
13580
  taskType: "service",
@@ -13364,7 +13625,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13364
13625
  throw translateError(e);
13365
13626
  }
13366
13627
  });
13367
- 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) => {
13628
+ 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) => {
13368
13629
  try {
13369
13630
  const signal = extra?.signal;
13370
13631
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`, {
@@ -13424,7 +13685,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13424
13685
  throw translateError(e);
13425
13686
  }
13426
13687
  });
13427
- 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) => {
13688
+ 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) => {
13428
13689
  try {
13429
13690
  const qs = new URLSearchParams();
13430
13691
  if (input.limit != null) qs.set("limit", String(input.limit));
@@ -13437,7 +13698,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13437
13698
  throw translateError(e);
13438
13699
  }
13439
13700
  });
13440
- 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) => {
13701
+ 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) => {
13441
13702
  try {
13442
13703
  const angle = await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/angles/${input.angleIndex}`, input.angle ?? {});
13443
13704
  return asText({
@@ -13447,315 +13708,331 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
13447
13708
  throw translateError(e);
13448
13709
  }
13449
13710
  });
13450
- 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) => {
13451
- try {
13452
- const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
13453
- const status = StatusForResolveSchema.parse(raw);
13454
- const pending = status.pendingUserAction;
13455
- if (!pending) {
13456
- throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
13457
- }
13458
- if (pending.kind !== input.action.kind) {
13459
- 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.`);
13460
- }
13461
- switch (input.action.kind) {
13462
- case "product_reference_set_approval": {
13463
- 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)) {
13464
- throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
13465
- }
13466
- if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
13467
- throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
13468
- }
13469
- if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
13470
- throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
13471
- }
13472
- if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
13473
- throw new Error("Corrige una sola vista por intento.");
13474
- }
13475
- if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
13476
- throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
13477
- }
13478
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
13479
- ...input.action,
13480
- ...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
13481
- selectedRefKeys: pending.selectedRefKeys
13482
- } : {}
13483
- });
13484
- return asText(result);
13711
+ server.tool(
13712
+ "resolve_pending_action",
13713
+ "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.",
13714
+ ResolvePendingActionInput.shape,
13715
+ // Approving the gate RELEASES the pipeline into paid generation at the video
13716
+ // and image providers (registry effects: credit_consuming + state_change),
13717
+ // and decision=reject discards the approved reference set. Both halves, hence
13718
+ // destructive AND open-world.
13719
+ OVERWRITES_EXTERNAL,
13720
+ async (input) => {
13721
+ try {
13722
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
13723
+ const status = StatusForResolveSchema.parse(raw);
13724
+ const pending = status.pendingUserAction;
13725
+ if (!pending) {
13726
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
13485
13727
  }
13486
- case "product_reference_confirmation": {
13487
- const candidateGrouping = pending.candidateGrouping;
13488
- const selectedImageUrls = pending.selectedImageUrls ?? [];
13489
- if (pending.mode === "visual_recovery") {
13490
- throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
13491
- }
13492
- if (!candidateGrouping || selectedImageUrls.length === 0) {
13493
- throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
13494
- }
13495
- const userChoice = input.action.userChoice;
13496
- if (!userChoice) {
13497
- throw new Error("La pregunta adaptativa necesita userChoice.");
13498
- }
13499
- const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
13500
- if (!offeredChoices) {
13501
- throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
13502
- }
13503
- if (!offeredChoices.includes(userChoice)) {
13504
- throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
13505
- }
13506
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
13507
- productReferenceConfirmation: {
13508
- candidateGrouping,
13509
- userChoice,
13510
- // Server overwrites with its own server-side computation;
13511
- // we send the matching MCP-mirror value so the wire body
13512
- // is internally coherent if the server skips overwrite.
13513
- selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
13514
- },
13515
- ...input.action.confirmedProductNames ? {
13516
- confirmedProductNames: input.action.confirmedProductNames
13517
- } : {},
13518
- ...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
13519
- decisionKey: pending.decisionKey,
13520
- referenceEvidenceVersion: pending.referenceEvidenceVersion,
13521
- expectedEvidenceHash: pending.expectedEvidenceHash
13522
- } : {},
13523
- // Multi-scene color showcase → upgrade to STANDARD. Gated to the
13524
- // variants choice client-side so the wire body stays coherent; the
13525
- // backend re-gates on variant_set grouping.
13526
- ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
13527
- videoTier: "standard"
13528
- } : {}
13529
- });
13530
- return asText(r);
13728
+ if (pending.kind !== input.action.kind) {
13729
+ 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.`);
13531
13730
  }
13532
- case "angle_review_approval": {
13533
- const body = {};
13534
- if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
13535
- body.customAngle = pending.customAngleDraft;
13731
+ switch (input.action.kind) {
13732
+ case "product_reference_set_approval": {
13733
+ 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)) {
13734
+ throw new Error("La aprobaci\xF3n de referencias no coincide con el snapshot mostrado; vuelve a llamar get_task_status.");
13735
+ }
13736
+ if (input.action.decision === "reject" && (!input.action.reason || !input.action.affectedRefKeys?.length || !input.action.recoveryAction)) {
13737
+ throw new Error("Una referencia rechazada necesita motivo, referencias afectadas y acci\xF3n de recuperaci\xF3n.");
13738
+ }
13739
+ if (input.action.decision === "correct_view" && (!input.action.reason || !input.action.affectedRefKeys?.length)) {
13740
+ throw new Error("Una correcci\xF3n de \xE1ngulo necesita motivo y referencias afectadas.");
13741
+ }
13742
+ if (input.action.decision === "correct_view" && input.action.affectedRefKeys?.length !== 1) {
13743
+ throw new Error("Corrige una sola vista por intento.");
13744
+ }
13745
+ if (pending.phase === "preparation" && input.action.decision === "approve" && (!pending.selectedRefKeys || pending.selectedRefKeys.length === 0)) {
13746
+ throw new Error("No hay fotos seleccionadas para preparar. Abre la tarea en ProductMaker Web y elige al menos una.");
13747
+ }
13748
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/resolve-product-reference-set`, {
13749
+ ...input.action,
13750
+ ...pending.phase === "preparation" && input.action.decision === "approve" && pending.selectedRefKeys ? {
13751
+ selectedRefKeys: pending.selectedRefKeys
13752
+ } : {}
13753
+ });
13754
+ return asText(result);
13536
13755
  }
13537
- if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
13538
- if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
13539
- if (pending.featuresDraft != null) body.features = [
13540
- ...pending.featuresDraft
13541
- ];
13542
- if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
13543
- if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
13544
- if (input.action.benefits != null) body.benefits = input.action.benefits;
13545
- if (input.action.features != null) body.features = input.action.features;
13546
- if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
13547
- throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
13756
+ case "product_reference_confirmation": {
13757
+ const candidateGrouping = pending.candidateGrouping;
13758
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
13759
+ if (pending.mode === "visual_recovery") {
13760
+ throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
13761
+ }
13762
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
13763
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
13764
+ }
13765
+ const userChoice = input.action.userChoice;
13766
+ if (!userChoice) {
13767
+ throw new Error("La pregunta adaptativa necesita userChoice.");
13768
+ }
13769
+ const offeredChoices = pending.question?.options.map((option) => option.value) ?? pending.options;
13770
+ if (!offeredChoices) {
13771
+ throw new Error("Estado inconsistente: falta la pregunta adaptativa con sus opciones verificables. Vuelve a llamar get_task_status.");
13772
+ }
13773
+ if (!offeredChoices.includes(userChoice)) {
13774
+ throw new Error(`La opci\xF3n "${userChoice}" no fue ofrecida en la pregunta pendiente. Elige una opci\xF3n mostrada por get_task_status.`);
13775
+ }
13776
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
13777
+ productReferenceConfirmation: {
13778
+ candidateGrouping,
13779
+ userChoice,
13780
+ // Server overwrites with its own server-side computation;
13781
+ // we send the matching MCP-mirror value so the wire body
13782
+ // is internally coherent if the server skips overwrite.
13783
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
13784
+ },
13785
+ ...input.action.confirmedProductNames ? {
13786
+ confirmedProductNames: input.action.confirmedProductNames
13787
+ } : {},
13788
+ ...pending.decisionKey && pending.referenceEvidenceVersion && pending.expectedEvidenceHash ? {
13789
+ decisionKey: pending.decisionKey,
13790
+ referenceEvidenceVersion: pending.referenceEvidenceVersion,
13791
+ expectedEvidenceHash: pending.expectedEvidenceHash
13792
+ } : {},
13793
+ // Multi-scene color showcase → upgrade to STANDARD. Gated to the
13794
+ // variants choice client-side so the wire body stays coherent; the
13795
+ // backend re-gates on variant_set grouping.
13796
+ ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
13797
+ videoTier: "standard"
13798
+ } : {}
13799
+ });
13800
+ return asText(r);
13548
13801
  }
13549
- if (input.action.gapDurationSeconds === 30) {
13550
- const choice = pending.gapDurationChoice;
13551
- if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
13552
- if (choice.recommendedSec !== 30) {
13553
- throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
13802
+ case "angle_review_approval": {
13803
+ const body = {};
13804
+ if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
13805
+ body.customAngle = pending.customAngleDraft;
13554
13806
  }
13555
- body.angleVideoOptions = [
13556
- {
13557
- angleIndex: choice.angleIndex,
13558
- ugcFormat: "double_30s"
13559
- }
13807
+ if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
13808
+ if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
13809
+ if (pending.featuresDraft != null) body.features = [
13810
+ ...pending.featuresDraft
13560
13811
  ];
13561
- }
13562
- const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
13563
- const resolvedTaskKind = resolveStatusTaskKind(status);
13564
- const isVideoTask = resolvedTaskKind === "video";
13565
- const isAutopilotTask = resolvedTaskKind === "autopilot";
13566
- if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
13567
- const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
13568
- const optionsByIndex = new Map(existingOptions.map((option) => [
13569
- option.angleIndex,
13570
- {
13571
- ...option
13812
+ if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
13813
+ if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
13814
+ if (input.action.benefits != null) body.benefits = input.action.benefits;
13815
+ if (input.action.features != null) body.features = input.action.features;
13816
+ if (input.action.gapDurationSeconds != null && !pending.gapDurationChoice) {
13817
+ throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para elegir una duraci\xF3n. Consulta get_task_status y confirma el winner primero.");
13818
+ }
13819
+ if (input.action.gapDurationSeconds === 30) {
13820
+ const choice = pending.gapDurationChoice;
13821
+ if (!choice) throw new Error("No hay una recomendaci\xF3n gapDurationChoice pendiente para aceptar 30s.");
13822
+ if (choice.recommendedSec !== 30) {
13823
+ throw new Error(`La recomendaci\xF3n de duraci\xF3n para este winner es ${choice.recommendedSec}s; no se puede solicitar 30s con este estado.`);
13572
13824
  }
13573
- ]));
13574
- for (const angleIndex of selectedAngleIndices) {
13575
- optionsByIndex.set(angleIndex, {
13576
- ...optionsByIndex.get(angleIndex) ?? {
13577
- angleIndex
13825
+ body.angleVideoOptions = [
13826
+ {
13827
+ angleIndex: choice.angleIndex,
13828
+ ugcFormat: "double_30s"
13829
+ }
13830
+ ];
13831
+ }
13832
+ const selectedAngleIndices = resolveSelectedAngleIndices(status, pending.gapDurationChoice);
13833
+ const resolvedTaskKind = resolveStatusTaskKind(status);
13834
+ const isVideoTask = resolvedTaskKind === "video";
13835
+ const isAutopilotTask = resolvedTaskKind === "autopilot";
13836
+ if ((isVideoTask || isAutopilotTask) && body.customAngle != null) {
13837
+ const existingOptions = Array.isArray(body.angleVideoOptions) ? body.angleVideoOptions : [];
13838
+ const optionsByIndex = new Map(existingOptions.map((option) => [
13839
+ option.angleIndex,
13840
+ {
13841
+ ...option
13842
+ }
13843
+ ]));
13844
+ for (const angleIndex of selectedAngleIndices) {
13845
+ optionsByIndex.set(angleIndex, {
13846
+ ...optionsByIndex.get(angleIndex) ?? {
13847
+ angleIndex
13848
+ },
13849
+ angle: body.customAngle
13850
+ });
13851
+ }
13852
+ body.angleVideoOptions = [
13853
+ ...optionsByIndex.values()
13854
+ ].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
13855
+ }
13856
+ if (isVideoTask) {
13857
+ body.selectedAngleIndices = selectedAngleIndices;
13858
+ const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
13859
+ return asText(r2);
13860
+ }
13861
+ if (isAutopilotTask) {
13862
+ const resumeBody = {
13863
+ selectedAngleIndices,
13864
+ ...status.ugcFormat ? {
13865
+ ugcFormat: status.ugcFormat
13866
+ } : {
13867
+ ugcFormat: "single_15s"
13578
13868
  },
13579
- angle: body.customAngle
13580
- });
13869
+ ...status.videoStyle ? {
13870
+ videoStyle: status.videoStyle
13871
+ } : {},
13872
+ ...status.videoModel ? {
13873
+ videoModel: status.videoModel
13874
+ } : {},
13875
+ ...status.videoTier ? {
13876
+ videoTier: status.videoTier
13877
+ } : {},
13878
+ ...status.videoResolution ? {
13879
+ videoResolution: status.videoResolution
13880
+ } : {},
13881
+ ...status.narrativeStyle ? {
13882
+ narrativeStyle: status.narrativeStyle
13883
+ } : {},
13884
+ ...body.customAngle != null ? {
13885
+ customAngleOverride: body.customAngle
13886
+ } : {},
13887
+ ...body.painPoints != null ? {
13888
+ painPoints: body.painPoints
13889
+ } : {},
13890
+ ...body.benefits != null ? {
13891
+ benefits: body.benefits
13892
+ } : {},
13893
+ ...body.angleVideoOptions ? {
13894
+ angleVideoOptions: body.angleVideoOptions
13895
+ } : {}
13896
+ };
13897
+ if (body.features !== void 0) {
13898
+ await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
13899
+ features: body.features
13900
+ });
13901
+ }
13902
+ const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
13903
+ if (r2?.success === false) {
13904
+ throw new Error(approveRefusalMessage(r2.reason));
13905
+ }
13906
+ return asText(r2);
13581
13907
  }
13582
- body.angleVideoOptions = [
13583
- ...optionsByIndex.values()
13584
- ].sort((left, right) => Number(left.angleIndex) - Number(right.angleIndex));
13585
- }
13586
- if (isVideoTask) {
13587
- body.selectedAngleIndices = selectedAngleIndices;
13588
- const r2 = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-video-angles`, body);
13589
- return asText(r2);
13590
- }
13591
- if (isAutopilotTask) {
13592
- const resumeBody = {
13593
- selectedAngleIndices,
13594
- ...status.ugcFormat ? {
13595
- ugcFormat: status.ugcFormat
13596
- } : {
13597
- ugcFormat: "single_15s"
13598
- },
13599
- ...status.videoStyle ? {
13600
- videoStyle: status.videoStyle
13601
- } : {},
13602
- ...status.videoModel ? {
13603
- videoModel: status.videoModel
13604
- } : {},
13605
- ...status.videoTier ? {
13606
- videoTier: status.videoTier
13607
- } : {},
13608
- ...status.videoResolution ? {
13609
- videoResolution: status.videoResolution
13610
- } : {},
13611
- ...status.narrativeStyle ? {
13612
- narrativeStyle: status.narrativeStyle
13613
- } : {},
13908
+ const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
13909
+ const imageBody = status.kind === "image" ? {
13614
13910
  ...body.customAngle != null ? {
13615
- customAngleOverride: body.customAngle
13911
+ customAngle: body.customAngle
13616
13912
  } : {},
13617
13913
  ...body.painPoints != null ? {
13618
13914
  painPoints: body.painPoints
13619
13915
  } : {},
13620
- ...body.benefits != null ? {
13621
- benefits: body.benefits
13622
- } : {},
13623
- ...body.angleVideoOptions ? {
13624
- angleVideoOptions: body.angleVideoOptions
13625
- } : {}
13626
- };
13627
- if (body.features !== void 0) {
13628
- await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/update-product-features`, {
13916
+ ...body.features != null ? {
13629
13917
  features: body.features
13630
- });
13918
+ } : {}
13919
+ } : body;
13920
+ const r = await api.postJson(route, imageBody);
13921
+ if (r?.success === false) {
13922
+ throw new Error(approveRefusalMessage(r.reason));
13631
13923
  }
13632
- const r2 = await api.postJson(`/v1/ingest/${encodeURIComponent(input.taskId)}/resume`, resumeBody);
13633
- return asText(r2);
13924
+ return asText(r);
13634
13925
  }
13635
- const route = resolvedTaskKind === "image" ? `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-image-angles` : `/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`;
13636
- const imageBody = status.kind === "image" ? {
13637
- ...body.customAngle != null ? {
13638
- customAngle: body.customAngle
13639
- } : {},
13640
- ...body.painPoints != null ? {
13641
- painPoints: body.painPoints
13642
- } : {},
13643
- ...body.features != null ? {
13644
- features: body.features
13645
- } : {}
13646
- } : body;
13647
- const r = await api.postJson(route, imageBody);
13648
- return asText(r);
13649
- }
13650
- case "service_angle_approval": {
13651
- const action = input.action;
13652
- const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
13653
- if (!candidate) {
13654
- throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
13926
+ case "service_angle_approval": {
13927
+ const action = input.action;
13928
+ const candidate = pending.angles?.find((angle) => angle.id === action.angleId);
13929
+ if (!candidate) {
13930
+ throw new Error("El angleId no pertenece a los conceptos pendientes de esta tarea.");
13931
+ }
13932
+ const edits = {
13933
+ content: action.content ?? candidate.content,
13934
+ creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
13935
+ actor: action.actor ?? candidate.actor,
13936
+ // Echo the persisted saved actor. Omitted when absent so the PATCH
13937
+ // never writes a null over a selection made elsewhere — the
13938
+ // backend only updates the keys it receives.
13939
+ ...candidate.actorId ? {
13940
+ actorId: candidate.actorId
13941
+ } : {}
13942
+ };
13943
+ await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
13944
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
13945
+ angleIds: [
13946
+ candidate.id
13947
+ ]
13948
+ });
13949
+ return asText(result);
13655
13950
  }
13656
- const edits = {
13657
- content: action.content ?? candidate.content,
13658
- creativeDirection: action.creativeDirection ?? candidate.creativeDirection,
13659
- actor: action.actor ?? candidate.actor,
13660
- // Echo the persisted saved actor. Omitted when absent so the PATCH
13661
- // never writes a null over a selection made elsewhere — the
13662
- // backend only updates the keys it receives.
13663
- ...candidate.actorId ? {
13664
- actorId: candidate.actorId
13665
- } : {}
13666
- };
13667
- await api.patchJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/${encodeURIComponent(candidate.id)}`, edits);
13668
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-angles/select`, {
13669
- angleIds: [
13670
- candidate.id
13671
- ]
13672
- });
13673
- return asText(result);
13674
- }
13675
- case "service_script_approval": {
13676
- const panels = pending.panels ?? [];
13677
- if (panels.length === 0) {
13678
- throw new Error("El storyboard pendiente no contiene paneles aprobables.");
13951
+ case "service_script_approval": {
13952
+ const panels = pending.panels ?? [];
13953
+ if (panels.length === 0) {
13954
+ throw new Error("El storyboard pendiente no contiene paneles aprobables.");
13955
+ }
13956
+ const voiceovers = {};
13957
+ const visualDirections = {};
13958
+ for (const panel of panels) {
13959
+ voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
13960
+ visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
13961
+ }
13962
+ const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
13963
+ voiceovers,
13964
+ visualDirections
13965
+ });
13966
+ return asText(result);
13679
13967
  }
13680
- const voiceovers = {};
13681
- const visualDirections = {};
13682
- for (const panel of panels) {
13683
- voiceovers[panel.beatId] = input.action.voiceovers?.[panel.beatId] ?? panel.voiceover;
13684
- visualDirections[panel.beatId] = input.action.visualDirections?.[panel.beatId] ?? panel.visualDirection;
13968
+ default: {
13969
+ const _exhaustive = input.action;
13970
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
13685
13971
  }
13686
- const result = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-script/approve`, {
13687
- voiceovers,
13688
- visualDirections
13689
- });
13690
- return asText(result);
13691
- }
13692
- default: {
13693
- const _exhaustive = input.action;
13694
- throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
13695
13972
  }
13973
+ } catch (e) {
13974
+ throw translateError(e);
13696
13975
  }
13697
- } catch (e) {
13698
- throw translateError(e);
13699
13976
  }
13700
- });
13977
+ );
13701
13978
  }
13702
13979
  __name(registerTaskTools, "registerTaskTools");
13703
13980
 
13704
13981
  // src/tools/creatives.ts
13705
13982
  import { randomUUID } from "crypto";
13706
- import { z as z50 } from "zod";
13983
+ import { z as z51 } from "zod";
13707
13984
  var CreativeRefs = {
13708
13985
  images: ImagesInput,
13709
13986
  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.'),
13710
13987
  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.'),
13711
- 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.'),
13712
- 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.'),
13713
- 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".'),
13714
- 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.'),
13715
- 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."),
13716
- 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.'),
13717
- 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.'),
13718
- isCombo: z50.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
13719
- 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."),
13720
- 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."),
13721
- 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."),
13722
- 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.'),
13723
- 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."),
13724
- 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.")
13988
+ price: z51.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.'),
13989
+ offer: z51.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.'),
13990
+ compareAtPrice: z51.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
13991
+ paymentMethods: z51.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.'),
13992
+ productContext: z51.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."),
13993
+ productLabel: z51.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.'),
13994
+ buyerPersona: z51.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.'),
13995
+ isCombo: z51.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
13996
+ competitorScanEnabled: z51.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."),
13997
+ brandId: z51.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."),
13998
+ projectId: z51.string().uuid().optional().describe("Opcional. ID de un proyecto guiado de dropshipping activo del usuario. Enlaza esta generaci\xF3n con su progreso y recompensas."),
13999
+ creativeDirection: z51.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.'),
14000
+ headline: z51.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."),
14001
+ subheadline: z51.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.")
13725
14002
  };
13726
- var VideoInput = z50.object({
14003
+ var VideoInput = z51.object({
13727
14004
  images: CreativeRefs.images,
13728
- title: z50.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
13729
- 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".'),
14005
+ title: z51.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
14006
+ primaryAngle: z51.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".'),
13730
14007
  language: CreativeRefs.language,
13731
14008
  country: CreativeRefs.country,
13732
- aspectRatio: z50.enum([
14009
+ aspectRatio: z51.enum([
13733
14010
  "9:16",
13734
14011
  "16:9",
13735
14012
  "Auto"
13736
14013
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
13737
- durationSeconds: z50.union([
13738
- z50.literal(15),
13739
- z50.literal(30)
14014
+ durationSeconds: z51.union([
14015
+ z51.literal(15),
14016
+ z51.literal(30)
13740
14017
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
13741
14018
  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).'),
13742
14019
  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.'),
13743
14020
  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).'),
13744
- 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"].'),
14021
+ features: z51.array(z51.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
13745
14022
  price: CreativeRefs.price,
13746
14023
  offer: CreativeRefs.offer,
13747
14024
  productContext: CreativeRefs.productContext,
13748
14025
  productLabel: CreativeRefs.productLabel,
13749
- 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.'),
13750
- 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.'),
13751
- 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."),
13752
- 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."),
13753
- 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."),
14026
+ cta: z51.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.'),
14027
+ scriptNotes: z51.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.'),
14028
+ pauseForVideoScriptReview: z51.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."),
14029
+ pauseForAngleReview: z51.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."),
14030
+ aiDecidesAll: z51.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."),
13754
14031
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
13755
14032
  brandId: CreativeRefs.brandId,
13756
14033
  projectId: CreativeRefs.projectId
13757
14034
  }).strict();
13758
- var FreeVideoQualityEnum = z50.enum([
14035
+ var FreeVideoQualityEnum = z51.enum([
13759
14036
  "economical",
13760
14037
  "high_detail"
13761
14038
  ]);
@@ -13777,38 +14054,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
13777
14054
  15
13778
14055
  ]
13779
14056
  };
13780
- var FreeVideoInput = z50.object({
14057
+ var FreeVideoInput = z51.object({
13781
14058
  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."),
13782
- 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."),
14059
+ rawPrompt: z51.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."),
13783
14060
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
13784
- 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."),
14061
+ durationSeconds: z51.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."),
13785
14062
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
13786
- generateAiMusic: z50.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
13787
- addSubtitles: z50.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
13788
- 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.")
14063
+ generateAiMusic: z51.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
14064
+ addSubtitles: z51.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
14065
+ idempotencyKey: z51.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.")
13789
14066
  }).strict();
13790
14067
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
13791
14068
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
13792
14069
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
13793
14070
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
13794
- var ImageInput = z50.object({
14071
+ var ImageInput = z51.object({
13795
14072
  images: CreativeRefs.images,
13796
- productTitle: z50.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
13797
- primaryAngle: z50.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
14073
+ productTitle: z51.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
14074
+ primaryAngle: z51.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
13798
14075
  buyerPersona: CreativeRefs.buyerPersona,
13799
14076
  language: CreativeRefs.language,
13800
14077
  country: CreativeRefs.country,
13801
- nVariants: z50.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
13802
- 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.'),
14078
+ nVariants: z51.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
14079
+ imageModel: z51.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
13803
14080
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
13804
14081
  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).`),
13805
14082
  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.`),
13806
- 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."),
14083
+ styleMix: z51.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
13807
14084
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
13808
- generateAdCopy: z50.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
13809
- 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."),
13810
- features: z50.array(z50.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
13811
- 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.'),
14085
+ generateAdCopy: z51.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
14086
+ pauseForAngleReview: z51.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."),
14087
+ features: z51.array(z51.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
14088
+ brandColors: z51.array(z51.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
13812
14089
  price: CreativeRefs.price,
13813
14090
  compareAtPrice: CreativeRefs.compareAtPrice,
13814
14091
  offer: CreativeRefs.offer,
@@ -13822,17 +14099,17 @@ var ImageInput = z50.object({
13822
14099
  headline: CreativeRefs.headline,
13823
14100
  subheadline: CreativeRefs.subheadline
13824
14101
  });
13825
- var EditImageCreativeInput = z50.object({
13826
- taskId: z50.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
13827
- variantIndex: z50.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
13828
- 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".')
14102
+ var EditImageCreativeInput = z51.object({
14103
+ taskId: z51.string().uuid().describe("ID de la tarea de imagen ya finalizada."),
14104
+ variantIndex: z51.number().int().min(0).max(10).describe("\xCDndice de la variante a corregir (0 = primera imagen)."),
14105
+ instruction: z51.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".')
13829
14106
  });
13830
- var LandingInput = z50.object({
13831
- 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."),
14107
+ var LandingInput = z51.object({
14108
+ aiDecidesAll: z51.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."),
13832
14109
  images: CreativeRefs.images,
13833
14110
  language: CreativeRefs.language,
13834
14111
  country: CreativeRefs.country,
13835
- 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.'),
14112
+ salesAngle: z51.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.'),
13836
14113
  buyerPersona: CreativeRefs.buyerPersona,
13837
14114
  price: CreativeRefs.price,
13838
14115
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -13847,11 +14124,12 @@ var LandingInput = z50.object({
13847
14124
  creativeDirection: CreativeRefs.creativeDirection,
13848
14125
  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".'),
13849
14126
  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".'),
14127
+ pauseForAngleReview: z51.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."),
13850
14128
  headline: CreativeRefs.headline,
13851
14129
  subheadline: CreativeRefs.subheadline
13852
14130
  });
13853
14131
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
13854
- 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) => {
14132
+ 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) => {
13855
14133
  try {
13856
14134
  const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
13857
14135
  if (!allowedDurations.includes(input.durationSeconds)) {
@@ -13881,7 +14159,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
13881
14159
  throw translateError(e);
13882
14160
  }
13883
14161
  });
13884
- 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) => {
14162
+ 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) => {
13885
14163
  try {
13886
14164
  const resolved = resolveVideoPreset(input.videoPreset);
13887
14165
  const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
@@ -13906,7 +14184,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
13906
14184
  throw translateError(e);
13907
14185
  }
13908
14186
  });
13909
- 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) => {
14187
+ 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) => {
13910
14188
  try {
13911
14189
  const { projectId, ...rest } = input;
13912
14190
  return await submitCreative(api, {
@@ -13924,7 +14202,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
13924
14202
  throw translateError(e);
13925
14203
  }
13926
14204
  });
13927
- 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) => {
14205
+ 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) => {
13928
14206
  try {
13929
14207
  const r = await api.postJson(`/v1/creatives/images/${encodeURIComponent(input.taskId)}/regenerate`, {
13930
14208
  comment: input.instruction,
@@ -13935,7 +14213,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
13935
14213
  throw translateError(e);
13936
14214
  }
13937
14215
  });
13938
- 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) => {
14216
+ 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) => {
13939
14217
  try {
13940
14218
  const { projectId, ...rest } = input;
13941
14219
  return await submitCreative(api, {
@@ -13957,8 +14235,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
13957
14235
  __name(registerCreativeTools, "registerCreativeTools");
13958
14236
 
13959
14237
  // src/tools/creative-variants.ts
13960
- import { z as z51 } from "zod";
13961
- var LandingSectionKeyEnum = z51.enum([
14238
+ import { z as z52 } from "zod";
14239
+ var LandingSectionKeyEnum = z52.enum([
13962
14240
  "problem_hook",
13963
14241
  "problem_cost",
13964
14242
  "solution_mechanism",
@@ -13998,24 +14276,24 @@ var LandingSectionKeyEnum = z51.enum([
13998
14276
  "lifestyle_fit",
13999
14277
  "join_lifestyle"
14000
14278
  ]);
14001
- var CreativeVariantKindEnum = z51.enum([
14279
+ var CreativeVariantKindEnum = z52.enum([
14002
14280
  "video",
14003
14281
  "image",
14004
14282
  "landing"
14005
14283
  ]);
14006
- var EstimateCreativeVariantInputBase = z51.object({
14007
- sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
14284
+ var EstimateCreativeVariantInputBase = z52.object({
14285
+ sourceTaskId: z52.string().uuid().describe("ID de la tarea original completada."),
14008
14286
  kind: CreativeVariantKindEnum,
14009
14287
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
14010
14288
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
14011
- nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
14289
+ nVariants: z52.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
14012
14290
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
14013
- 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.")
14291
+ instructions: z52.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.")
14014
14292
  }).strict();
14015
14293
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
14016
14294
  if (input.kind === "video" && input.nVariants !== void 0) {
14017
14295
  ctx.addIssue({
14018
- code: z51.ZodIssueCode.custom,
14296
+ code: z52.ZodIssueCode.custom,
14019
14297
  path: [
14020
14298
  "nVariants"
14021
14299
  ],
@@ -14025,7 +14303,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14025
14303
  if (input.kind !== "video") {
14026
14304
  if (input.videoPreset !== void 0) {
14027
14305
  ctx.addIssue({
14028
- code: z51.ZodIssueCode.custom,
14306
+ code: z52.ZodIssueCode.custom,
14029
14307
  path: [
14030
14308
  "videoPreset"
14031
14309
  ],
@@ -14034,7 +14312,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14034
14312
  }
14035
14313
  if (input.videoResolution !== void 0) {
14036
14314
  ctx.addIssue({
14037
- code: z51.ZodIssueCode.custom,
14315
+ code: z52.ZodIssueCode.custom,
14038
14316
  path: [
14039
14317
  "videoResolution"
14040
14318
  ],
@@ -14044,7 +14322,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14044
14322
  }
14045
14323
  if (input.kind !== "image" && input.nVariants !== void 0) {
14046
14324
  ctx.addIssue({
14047
- code: z51.ZodIssueCode.custom,
14325
+ code: z52.ZodIssueCode.custom,
14048
14326
  path: [
14049
14327
  "nVariants"
14050
14328
  ],
@@ -14054,7 +14332,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14054
14332
  if (input.kind !== "landing") {
14055
14333
  if (input.sectionKey !== void 0) {
14056
14334
  ctx.addIssue({
14057
- code: z51.ZodIssueCode.custom,
14335
+ code: z52.ZodIssueCode.custom,
14058
14336
  path: [
14059
14337
  "sectionKey"
14060
14338
  ],
@@ -14063,7 +14341,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14063
14341
  }
14064
14342
  if (input.instructions !== void 0) {
14065
14343
  ctx.addIssue({
14066
- code: z51.ZodIssueCode.custom,
14344
+ code: z52.ZodIssueCode.custom,
14067
14345
  path: [
14068
14346
  "instructions"
14069
14347
  ],
@@ -14072,31 +14350,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
14072
14350
  }
14073
14351
  }
14074
14352
  });
14075
- var CreateCreativeVariantInputBase = z51.object({
14076
- sourceTaskId: z51.string().uuid().describe("ID de la tarea original completada."),
14353
+ var CreateCreativeVariantInputBase = z52.object({
14354
+ sourceTaskId: z52.string().uuid().describe("ID de la tarea original completada."),
14077
14355
  kind: CreativeVariantKindEnum,
14078
- confirm: z51.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
14079
- 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."),
14080
- angle: z51.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
14081
- buyerPersona: z51.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
14082
- scriptNotes: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
14083
- creativeDirection: z51.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
14356
+ confirm: z52.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
14357
+ clientRequestId: z52.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."),
14358
+ angle: z52.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
14359
+ buyerPersona: z52.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
14360
+ scriptNotes: z52.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
14361
+ creativeDirection: z52.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
14084
14362
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
14085
14363
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
14086
- generateAiMusic: z51.boolean().optional().describe("Opcional para video."),
14087
- nVariants: z51.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
14364
+ generateAiMusic: z52.boolean().optional().describe("Opcional para video."),
14365
+ nVariants: z52.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
14088
14366
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
14089
14367
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
14090
14368
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
14091
14369
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
14092
- styleMix: z51.boolean().optional().describe("Opcional para imagen."),
14370
+ styleMix: z52.boolean().optional().describe("Opcional para imagen."),
14093
14371
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
14094
- 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.")
14372
+ instructions: z52.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.")
14095
14373
  }).strict();
14096
14374
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
14097
14375
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
14098
14376
  ctx.addIssue({
14099
- code: z51.ZodIssueCode.custom,
14377
+ code: z52.ZodIssueCode.custom,
14100
14378
  path: [
14101
14379
  "angle"
14102
14380
  ],
@@ -14105,7 +14383,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
14105
14383
  }
14106
14384
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
14107
14385
  ctx.addIssue({
14108
- code: z51.ZodIssueCode.custom,
14386
+ code: z52.ZodIssueCode.custom,
14109
14387
  path: [
14110
14388
  "instructions"
14111
14389
  ],
@@ -14151,7 +14429,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
14151
14429
  for (const field of fieldsByKind[input.kind]) {
14152
14430
  if (input[field] !== void 0) {
14153
14431
  ctx.addIssue({
14154
- code: z51.ZodIssueCode.custom,
14432
+ code: z52.ZodIssueCode.custom,
14155
14433
  path: [
14156
14434
  field
14157
14435
  ],
@@ -14185,7 +14463,7 @@ function estimatePayload(input) {
14185
14463
  }
14186
14464
  __name(estimatePayload, "estimatePayload");
14187
14465
  function registerCreativeVariantTools(server, api) {
14188
- 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) => {
14466
+ 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) => {
14189
14467
  try {
14190
14468
  const validatedInput = parseVariantInput(EstimateCreativeVariantInput, input);
14191
14469
  const estimate = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants/estimate`, estimatePayload(validatedInput));
@@ -14198,7 +14476,7 @@ function registerCreativeVariantTools(server, api) {
14198
14476
  throw translateError(error);
14199
14477
  }
14200
14478
  });
14201
- 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) => {
14479
+ 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) => {
14202
14480
  try {
14203
14481
  const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
14204
14482
  const estimateInput = EstimateCreativeVariantInput.parse({
@@ -14268,31 +14546,31 @@ function registerCreativeVariantTools(server, api) {
14268
14546
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
14269
14547
 
14270
14548
  // src/tools/script-review.ts
14271
- import { z as z52 } from "zod";
14272
- var GetVideoScriptInput = z52.object({
14273
- taskId: z52.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
14274
- 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.')
14549
+ import { z as z53 } from "zod";
14550
+ var GetVideoScriptInput = z53.object({
14551
+ taskId: z53.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
14552
+ angleKey: z53.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.')
14275
14553
  }).strict();
14276
14554
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
14277
14555
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
14278
- var ApproveSceneEdit = z52.object({
14279
- index: z52.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
14280
- dialogue: z52.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
14556
+ var ApproveSceneEdit = z53.object({
14557
+ index: z53.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
14558
+ dialogue: z53.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
14281
14559
  message: "dialogue no puede ser s\xF3lo espacios"
14282
14560
  }).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.")
14283
14561
  }).strict();
14284
- var ApproveVideoScriptInput = z52.object({
14285
- taskId: z52.string().min(1).describe("ID de la tarea."),
14286
- 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.'),
14287
- 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.")
14562
+ var ApproveVideoScriptInput = z53.object({
14563
+ taskId: z53.string().min(1).describe("ID de la tarea."),
14564
+ angleKey: z53.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.'),
14565
+ scenes: z53.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.")
14288
14566
  }).strict();
14289
- var RegenerateVideoScriptInput = z52.object({
14290
- taskId: z52.string().min(1).describe("ID de la tarea."),
14291
- 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.'),
14567
+ var RegenerateVideoScriptInput = z53.object({
14568
+ taskId: z53.string().min(1).describe("ID de la tarea."),
14569
+ angleKey: z53.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.'),
14292
14570
  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).')
14293
14571
  }).strict();
14294
14572
  function registerScriptReviewTools(server, api) {
14295
- 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) => {
14573
+ 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) => {
14296
14574
  try {
14297
14575
  const qs = input.angleKey ? `?angleKey=${encodeURIComponent(input.angleKey)}` : "";
14298
14576
  const r = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script${qs}`);
@@ -14301,7 +14579,7 @@ function registerScriptReviewTools(server, api) {
14301
14579
  throw translateError(e);
14302
14580
  }
14303
14581
  });
14304
- 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) => {
14582
+ 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) => {
14305
14583
  try {
14306
14584
  const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/regenerate`, {
14307
14585
  angleKey: input.angleKey,
@@ -14312,25 +14590,36 @@ function registerScriptReviewTools(server, api) {
14312
14590
  throw translateError(e);
14313
14591
  }
14314
14592
  });
14315
- 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) => {
14316
- try {
14317
- const body = {
14318
- scenes: input.scenes
14319
- };
14320
- if (input.angleKey) body.angleKey = input.angleKey;
14321
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
14322
- return asText(r);
14323
- } catch (e) {
14324
- throw translateError(e);
14593
+ server.tool(
14594
+ "approve_video_script",
14595
+ '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.',
14596
+ ApproveVideoScriptInput.shape,
14597
+ // Not CREATES_INTERNAL: approving the script is what STARTS the render, so
14598
+ // this call dispatches jobs to the video providers and burns credits. Its own
14599
+ // description says so ("Tras llamarlo, el render arranca y consume créditos").
14600
+ // A host that decides when to interrupt the user by `destructiveHint ||
14601
+ // openWorldHint` would otherwise auto-invoke the one call that spends money.
14602
+ CREATES_EXTERNAL,
14603
+ async (input) => {
14604
+ try {
14605
+ const body = {
14606
+ scenes: input.scenes
14607
+ };
14608
+ if (input.angleKey) body.angleKey = input.angleKey;
14609
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/video-script/approve`, body);
14610
+ return asText(r);
14611
+ } catch (e) {
14612
+ throw translateError(e);
14613
+ }
14325
14614
  }
14326
- });
14615
+ );
14327
14616
  }
14328
14617
  __name(registerScriptReviewTools, "registerScriptReviewTools");
14329
14618
 
14330
14619
  // src/tools/connections.ts
14331
- import { z as z53 } from "zod";
14620
+ import { z as z54 } from "zod";
14332
14621
  function registerConnectionTools(server, api) {
14333
- 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, async () => {
14622
+ 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.", z54.object({}).shape, READS_INTERNAL, async () => {
14334
14623
  try {
14335
14624
  const r = await api.getJson("/v1/shopify/shops");
14336
14625
  return asText(r);
@@ -14338,11 +14627,11 @@ function registerConnectionTools(server, api) {
14338
14627
  throw translateError(e);
14339
14628
  }
14340
14629
  });
14341
- 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({
14342
- shopDomain: z53.string().min(3).max(120),
14343
- query: z53.string().trim().max(120).optional(),
14344
- taskId: z53.string().uuid().optional()
14345
- }).shape, async ({ shopDomain, query, taskId }) => {
14630
+ 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.', z54.object({
14631
+ shopDomain: z54.string().min(3).max(120),
14632
+ query: z54.string().trim().max(120).optional(),
14633
+ taskId: z54.string().uuid().optional()
14634
+ }).shape, READS_EXTERNAL, async ({ shopDomain, query, taskId }) => {
14346
14635
  try {
14347
14636
  const params = new URLSearchParams({
14348
14637
  shopDomain
@@ -14355,7 +14644,7 @@ function registerConnectionTools(server, api) {
14355
14644
  throw translateError(e);
14356
14645
  }
14357
14646
  });
14358
- 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, async () => {
14647
+ 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.", z54.object({}).shape, READS_EXTERNAL, async () => {
14359
14648
  try {
14360
14649
  const r = await api.getJson("/v1/facebook/ad-accounts");
14361
14650
  return asText(r);
@@ -14365,7 +14654,8 @@ function registerConnectionTools(server, api) {
14365
14654
  });
14366
14655
  server.registerTool("list_meta_destinations", {
14367
14656
  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.",
14368
- inputSchema: z53.object({}).shape,
14657
+ inputSchema: z54.object({}).shape,
14658
+ annotations: READS_EXTERNAL,
14369
14659
  _meta: {
14370
14660
  "openai/widgetAccessible": true
14371
14661
  }
@@ -14377,7 +14667,7 @@ function registerConnectionTools(server, api) {
14377
14667
  throw translateError(e);
14378
14668
  }
14379
14669
  });
14380
- 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, async () => {
14670
+ 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.", z54.object({}).shape, READS_EXTERNAL, async () => {
14381
14671
  try {
14382
14672
  const r = await api.getJson("/v1/tiktok/advertisers");
14383
14673
  return asText(r);
@@ -14389,9 +14679,9 @@ function registerConnectionTools(server, api) {
14389
14679
  __name(registerConnectionTools, "registerConnectionTools");
14390
14680
 
14391
14681
  // src/tools/actors.ts
14392
- import { z as z54 } from "zod";
14682
+ import { z as z55 } from "zod";
14393
14683
  function registerActorTools(server, api) {
14394
- 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, async () => {
14684
+ 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.", z55.object({}).shape, READS_INTERNAL, async () => {
14395
14685
  try {
14396
14686
  const rows = await api.getJson("/v1/actors");
14397
14687
  const actors = (Array.isArray(rows) ? rows : []).filter((actor) => actor.status === "ready").map(({ id, name, description, gender, voiceId }) => ({
@@ -14412,116 +14702,145 @@ function registerActorTools(server, api) {
14412
14702
  __name(registerActorTools, "registerActorTools");
14413
14703
 
14414
14704
  // src/tools/publish.ts
14415
- import { z as z55 } from "zod";
14416
- var ShopifyInput = z55.object({
14417
- taskId: z55.string().min(1),
14418
- shopDomain: z55.string().min(1),
14705
+ import { z as z56 } from "zod";
14706
+ var ShopifyInput = z56.object({
14707
+ taskId: z56.string().min(1),
14708
+ shopDomain: z56.string().min(1),
14419
14709
  /** Override: number of landing images to include. Default = ALL images the
14420
14710
  * task generated (the MCP fetches /v1/results first to count). Bounded
14421
14711
  * generously here; the backend validates each index against
14422
14712
  * landing.images.length and rejects out-of-range. */
14423
- numLandingImages: z55.number().int().min(1).max(20).optional(),
14424
- variantIndex: z55.number().int().min(0).optional(),
14713
+ numLandingImages: z56.number().int().min(1).max(20).optional(),
14714
+ variantIndex: z56.number().int().min(0).optional(),
14425
14715
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
14426
14716
  * When true, the landing renders variant-picker pills; when false, it ships
14427
14717
  * a single-option product page. Defaults to false. */
14428
- showVariantPicker: z55.boolean().optional(),
14718
+ showVariantPicker: z56.boolean().optional(),
14429
14719
  /** Default `auto` updates the product previously published for this task and
14430
14720
  * shop. Use `existing` with targetProductId to choose another product, or
14431
14721
  * `new` only when the user explicitly wants a duplicate/new listing. */
14432
- publicationMode: z55.enum([
14722
+ publicationMode: z56.enum([
14433
14723
  "auto",
14434
14724
  "existing",
14435
14725
  "new"
14436
14726
  ]).optional(),
14437
14727
  /** Shopify product GID, numeric id, or admin product URL. Required when
14438
14728
  * publicationMode is `existing`. */
14439
- targetProductId: z55.string().trim().min(1).max(300).optional()
14729
+ targetProductId: z56.string().trim().min(1).max(300).optional(),
14730
+ /** Add an announcement bar text above add-to-cart (optional). */
14731
+ announcementBarEnabled: z56.boolean().optional(),
14732
+ announcementBarText: z56.string().trim().min(1).max(SHOPIFY_ANNOUNCEMENT_BAR_MAX_LENGTH).optional()
14733
+ });
14734
+ var ShopifyInputWithAnnouncementBarValidation = ShopifyInput.superRefine((value, ctx) => {
14735
+ if (value.announcementBarEnabled && !value.announcementBarText) {
14736
+ ctx.addIssue({
14737
+ code: z56.ZodIssueCode.custom,
14738
+ path: [
14739
+ "announcementBarText"
14740
+ ],
14741
+ message: "announcementBarText is required when announcementBarEnabled is true"
14742
+ });
14743
+ }
14744
+ if (!value.announcementBarEnabled && value.announcementBarText) {
14745
+ ctx.addIssue({
14746
+ code: z56.ZodIssueCode.custom,
14747
+ path: [
14748
+ "announcementBarText"
14749
+ ],
14750
+ message: "announcementBarText requires announcementBarEnabled to be true"
14751
+ });
14752
+ }
14440
14753
  });
14441
- var McpCampaignConfig = z55.object({
14754
+ function parseShopifyInput(rawInput) {
14755
+ const parsed = ShopifyInputWithAnnouncementBarValidation.safeParse(rawInput);
14756
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
14757
+ return parsed.data;
14758
+ }
14759
+ __name(parseShopifyInput, "parseShopifyInput");
14760
+ var McpCampaignConfig = z56.object({
14442
14761
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
14443
14762
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
14444
14763
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
14445
- dailyBudgetUsd: z55.number().positive().optional(),
14764
+ dailyBudgetUsd: z56.number().positive().optional(),
14446
14765
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
14447
14766
  * ad set). TikTok takes only the first entry. */
14448
- targetCountries: z55.array(z55.string().length(2)).max(1).optional(),
14449
- campaignName: z55.string().trim().min(1).max(100).optional()
14767
+ targetCountries: z56.array(z56.string().length(2)).max(1).optional(),
14768
+ campaignName: z56.string().trim().min(1).max(100).optional()
14450
14769
  });
14451
14770
  var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
14452
- var MetaInput = z55.object({
14453
- taskId: z55.string().min(1),
14771
+ var MetaInput = z56.object({
14772
+ taskId: z56.string().min(1),
14454
14773
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
14455
- portfolioId: z55.string().trim().min(1),
14774
+ portfolioId: z56.string().trim().min(1),
14456
14775
  /** Exact Facebook Page selected from `list_meta_destinations`. */
14457
- pageId: z55.string().trim().min(1),
14776
+ pageId: z56.string().trim().min(1),
14458
14777
  adAccountId: McpAdAccountId,
14459
- pixelId: z55.string().min(1).optional(),
14460
- landingUrl: z55.string().url().optional(),
14778
+ pixelId: z56.string().min(1).optional(),
14779
+ landingUrl: z56.string().url().optional(),
14461
14780
  /** Select which angles to publish by 0-based index. Omit to publish all
14462
14781
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
14463
- angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
14782
+ angleIndexes: z56.array(z56.number().int().nonnegative()).optional(),
14464
14783
  campaignConfig: McpCampaignConfig.optional(),
14465
14784
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
14466
14785
  * uses the auto-generated copy attached to the task. Capped at 500 chars
14467
14786
  * (Meta's primary-text limit). */
14468
- primaryText: z55.string().trim().min(1).max(500).optional(),
14787
+ primaryText: z56.string().trim().min(1).max(500).optional(),
14469
14788
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
14470
14789
  * (wa.me) instead of a landing page. */
14471
- phoneNumber: z55.string().min(8).max(32).optional()
14790
+ phoneNumber: z56.string().min(8).max(32).optional()
14472
14791
  });
14473
- var GroupedTaskSource = z55.object({
14474
- type: z55.literal("task"),
14475
- taskId: z55.string().min(1),
14476
- angleIndex: z55.number().int().min(0).optional(),
14477
- videoVariant: z55.enum([
14792
+ var GroupedTaskSource = z56.object({
14793
+ type: z56.literal("task"),
14794
+ taskId: z56.string().min(1),
14795
+ angleIndex: z56.number().int().min(0).optional(),
14796
+ videoVariant: z56.enum([
14478
14797
  "with_subtitles",
14479
14798
  "without_subtitles"
14480
14799
  ]).optional(),
14481
- imageVariantIndex: z55.number().int().min(0).optional()
14482
- });
14483
- var GroupedMediaSource = z55.object({
14484
- type: z55.literal("media"),
14485
- 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."),
14486
- 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".'),
14487
- thumbnailUrl: z55.string().url().optional(),
14488
- thumbnailPath: z55.string().min(1).optional()
14800
+ imageVariantIndex: z56.number().int().min(0).optional()
14801
+ });
14802
+ var GroupedMediaSource = z56.object({
14803
+ type: z56.literal("media"),
14804
+ url: z56.string().url().optional().describe("Public HTTPS media URL. For a file on the user laptop, use request_media_upload on the hosted MCP instead."),
14805
+ path: z56.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".'),
14806
+ thumbnailUrl: z56.string().url().optional(),
14807
+ thumbnailPath: z56.string().min(1).optional()
14489
14808
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
14490
14809
  message: "media source needs exactly one of `url` or `path`"
14491
14810
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
14492
14811
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
14493
14812
  });
14494
- var GroupedUploadedAssetSource = z55.object({
14495
- type: z55.literal("asset"),
14813
+ var GroupedUploadedAssetSource = z56.object({
14814
+ type: z56.literal("asset"),
14496
14815
  /** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
14497
- assetId: z55.string().uuid(),
14498
- thumbnailAssetId: z55.string().uuid().optional()
14816
+ assetId: z56.string().uuid(),
14817
+ thumbnailAssetId: z56.string().uuid().optional()
14499
14818
  });
14500
- var GroupedCreative = z55.object({
14501
- type: z55.enum([
14819
+ var GroupedCreative = z56.object({
14820
+ type: z56.enum([
14502
14821
  "image",
14503
14822
  "video"
14504
14823
  ]),
14505
- source: z55.union([
14824
+ source: z56.union([
14506
14825
  GroupedTaskSource,
14507
14826
  GroupedMediaSource,
14508
14827
  GroupedUploadedAssetSource
14509
14828
  ]),
14510
- title: z55.string().max(120).optional(),
14511
- description: z55.string().max(500).optional(),
14829
+ title: z56.string().max(120).optional(),
14830
+ description: z56.string().max(500).optional(),
14512
14831
  cta: MetaCtaType.optional(),
14513
- creativeId: z55.string().min(1).optional()
14832
+ creativeId: z56.string().min(1).optional()
14514
14833
  });
14515
- var GroupedCampaignConfigBase = z55.object({
14834
+ var GroupedCampaignConfigBase = z56.object({
14516
14835
  campaignType: MetaCampaignType.default("sales"),
14517
14836
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
14518
14837
  budgetMode: MetaBudgetMode.optional(),
14519
- campaignDailyBudget: z55.number().int().positive().optional(),
14520
- adSets: z55.array(z55.object({
14521
- tempId: z55.string().min(1).max(80),
14522
- name: z55.string().min(1).max(100),
14523
- dailyBudget: z55.number().int().positive().optional(),
14524
- creativeIds: z55.array(z55.string().min(1)).min(1)
14838
+ campaignDailyBudget: z56.number().int().positive().optional(),
14839
+ adSets: z56.array(z56.object({
14840
+ tempId: z56.string().min(1).max(80),
14841
+ name: z56.string().min(1).max(100),
14842
+ dailyBudget: z56.number().int().positive().optional(),
14843
+ creativeIds: z56.array(z56.string().min(1)).min(1)
14525
14844
  })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
14526
14845
  /**
14527
14846
  * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
@@ -14529,17 +14848,17 @@ var GroupedCampaignConfigBase = z55.object({
14529
14848
  * opaque `invalid_literal` and made the WhatsApp destination — supported by
14530
14849
  * the API since #849 — unreachable from this tool.
14531
14850
  */
14532
- destination: z55.discriminatedUnion("type", [
14533
- z55.object({
14534
- type: z55.literal("landing"),
14535
- landingUrl: z55.string().url()
14851
+ destination: z56.discriminatedUnion("type", [
14852
+ z56.object({
14853
+ type: z56.literal("landing"),
14854
+ landingUrl: z56.string().url()
14536
14855
  }),
14537
- z55.object({
14538
- type: z55.literal("whatsapp"),
14539
- phoneNumber: z55.string().min(8).max(32)
14856
+ z56.object({
14857
+ type: z56.literal("whatsapp"),
14858
+ phoneNumber: z56.string().min(8).max(32)
14540
14859
  })
14541
14860
  ]),
14542
- saveAsDefault: z55.boolean().default(false),
14861
+ saveAsDefault: z56.boolean().default(false),
14543
14862
  budgetPolicy: MetaBudgetPolicy.optional()
14544
14863
  });
14545
14864
  function resolveBudgetMode(value) {
@@ -14553,7 +14872,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
14553
14872
  const budgetMode = resolveBudgetMode(value);
14554
14873
  if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
14555
14874
  ctx.addIssue({
14556
- code: z55.ZodIssueCode.custom,
14875
+ code: z56.ZodIssueCode.custom,
14557
14876
  path: [
14558
14877
  "campaignDailyBudget"
14559
14878
  ],
@@ -14564,7 +14883,7 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
14564
14883
  value.adSets.forEach((adSet, index) => {
14565
14884
  if (adSet.dailyBudget === void 0) {
14566
14885
  ctx.addIssue({
14567
- code: z55.ZodIssueCode.custom,
14886
+ code: z56.ZodIssueCode.custom,
14568
14887
  path: [
14569
14888
  "adSets",
14570
14889
  index,
@@ -14579,26 +14898,26 @@ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) =
14579
14898
  ...value,
14580
14899
  budgetMode: resolveBudgetMode(value)
14581
14900
  }));
14582
- var PublishCreativesToMetaInputBase = z55.object({
14583
- creatives: z55.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
14901
+ var PublishCreativesToMetaInputBase = z56.object({
14902
+ creatives: z56.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
14584
14903
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
14585
- portfolioId: z55.string().trim().min(1),
14904
+ portfolioId: z56.string().trim().min(1),
14586
14905
  /** Exact Facebook Page selected from `list_meta_destinations`. */
14587
- pageId: z55.string().trim().min(1),
14906
+ pageId: z56.string().trim().min(1),
14588
14907
  adAccountId: McpAdAccountId,
14589
- pixelId: z55.string().min(1).optional(),
14908
+ pixelId: z56.string().min(1).optional(),
14590
14909
  /** Required unless `campaignConfig.destination` is whatsapp — see the
14591
14910
  * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
14592
- landingUrl: z55.string().url().optional(),
14593
- targetCountry: z55.string().length(2),
14594
- campaignName: z55.string().trim().min(1).max(100),
14911
+ landingUrl: z56.string().url().optional(),
14912
+ targetCountry: z56.string().length(2),
14913
+ campaignName: z56.string().trim().min(1).max(100),
14595
14914
  campaignConfig: GroupedCampaignConfig.optional()
14596
14915
  });
14597
14916
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
14598
14917
  const destination = value.campaignConfig?.destination;
14599
14918
  if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
14600
14919
  ctx.addIssue({
14601
- code: z55.ZodIssueCode.custom,
14920
+ code: z56.ZodIssueCode.custom,
14602
14921
  path: [
14603
14922
  "landingUrl"
14604
14923
  ],
@@ -14607,7 +14926,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
14607
14926
  }
14608
14927
  if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
14609
14928
  ctx.addIssue({
14610
- code: z55.ZodIssueCode.custom,
14929
+ code: z56.ZodIssueCode.custom,
14611
14930
  path: [
14612
14931
  "campaignConfig",
14613
14932
  "destination",
@@ -14626,16 +14945,16 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
14626
14945
  }
14627
14946
  return value;
14628
14947
  });
14629
- var PresignUploadResponseSchema = z55.object({
14630
- uploadUrl: z55.string().url().refine((value) => {
14948
+ var PresignUploadResponseSchema = z56.object({
14949
+ uploadUrl: z56.string().url().refine((value) => {
14631
14950
  const url = new URL(value);
14632
14951
  return !url.username && !url.password && url.protocol === "https:";
14633
14952
  }, "presigned upload URL must be HTTPS without embedded credentials"),
14634
- publicUrl: z55.string().url().refine((value) => {
14953
+ publicUrl: z56.string().url().refine((value) => {
14635
14954
  const url = new URL(value);
14636
14955
  return !url.username && !url.password;
14637
14956
  }, "public media URL must not contain embedded credentials"),
14638
- stagingAssetId: z55.string().uuid()
14957
+ stagingAssetId: z56.string().uuid()
14639
14958
  });
14640
14959
  async function uploadResolvedMedia(api, media, assetRole) {
14641
14960
  if (!media.buffer) throw new ResolveMediaError("INVALID_INPUT", "resolved media has no bytes");
@@ -14660,22 +14979,23 @@ async function resolveAndUploadMedia(api, ref, expectedKind, assetRole, transpor
14660
14979
  return uploadResolvedMedia(api, media, assetRole);
14661
14980
  }
14662
14981
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
14663
- var TikTokInput = z55.object({
14664
- taskId: z55.string().min(1),
14665
- advertiserId: z55.string().min(1),
14666
- pixelId: z55.string().min(1).optional(),
14667
- landingUrl: z55.string().url().optional(),
14668
- angleIndexes: z55.array(z55.number().int().nonnegative()).optional(),
14982
+ var TikTokInput = z56.object({
14983
+ taskId: z56.string().min(1),
14984
+ advertiserId: z56.string().min(1),
14985
+ pixelId: z56.string().min(1).optional(),
14986
+ landingUrl: z56.string().url().optional(),
14987
+ angleIndexes: z56.array(z56.number().int().nonnegative()).optional(),
14669
14988
  campaignConfig: McpCampaignConfig.optional(),
14670
14989
  /** Override the ad's description (TikTok's body copy). If omitted,
14671
14990
  * ProductMaker uses the auto-generated copy attached to the task. */
14672
- primaryText: z55.string().trim().min(1).max(500).optional()
14991
+ primaryText: z56.string().trim().min(1).max(500).optional()
14673
14992
  });
14674
14993
  function registerPublishTools(server, api, opts = {}) {
14675
14994
  const transport = opts.transport ?? "http";
14676
- 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) => {
14995
+ 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) => {
14677
14996
  try {
14678
- if (input.publicationMode === "existing" && !input.targetProductId) {
14997
+ const parsed = parseShopifyInput(input);
14998
+ if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
14679
14999
  return {
14680
15000
  isError: true,
14681
15001
  content: [
@@ -14686,29 +15006,31 @@ function registerPublishTools(server, api, opts = {}) {
14686
15006
  ]
14687
15007
  };
14688
15008
  }
14689
- const variantIndex = input.variantIndex ?? 0;
14690
- const availableImages = await fetchLandingImageCount(api, input.taskId);
15009
+ const variantIndex = parsed.variantIndex ?? 0;
15010
+ const availableImages = await fetchLandingImageCount(api, parsed.taskId);
14691
15011
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
14692
- const numImages = Math.min(input.numLandingImages ?? effectiveAvailable, effectiveAvailable);
15012
+ const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
14693
15013
  const selectedLandingImages = Array.from({
14694
15014
  length: numImages
14695
15015
  }, (_, i) => ({
14696
15016
  imageIndex: i,
14697
15017
  variantIndex
14698
15018
  }));
14699
- const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/publish/shopify`, {
14700
- shopDomain: input.shopDomain,
15019
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(parsed.taskId)}/publish/shopify`, {
15020
+ shopDomain: parsed.shopDomain,
14701
15021
  selectedLandingImages,
14702
- showVariantPicker: input.showVariantPicker,
14703
- publicationMode: input.publicationMode,
14704
- targetProductId: input.targetProductId
15022
+ showVariantPicker: parsed.showVariantPicker,
15023
+ publicationMode: parsed.publicationMode,
15024
+ targetProductId: parsed.targetProductId,
15025
+ announcementBarEnabled: parsed.announcementBarEnabled,
15026
+ announcementBarText: parsed.announcementBarText
14705
15027
  });
14706
15028
  return renderShopifyPublishMarkdown(r);
14707
15029
  } catch (e) {
14708
15030
  throw translateError(e);
14709
15031
  }
14710
15032
  });
14711
- 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 }) => {
15033
+ 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 }) => {
14712
15034
  try {
14713
15035
  const body = {
14714
15036
  ...rest
@@ -14736,7 +15058,7 @@ function registerPublishTools(server, api, opts = {}) {
14736
15058
  throw translateError(e);
14737
15059
  }
14738
15060
  });
14739
- 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) => {
15061
+ 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) => {
14740
15062
  try {
14741
15063
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
14742
15064
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -14826,7 +15148,7 @@ function registerPublishTools(server, api, opts = {}) {
14826
15148
  throw translateError(e);
14827
15149
  }
14828
15150
  });
14829
- 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 }) => {
15151
+ 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 }) => {
14830
15152
  try {
14831
15153
  const body = {
14832
15154
  ...rest
@@ -14916,38 +15238,38 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
14916
15238
 
14917
15239
  // src/tools/meta-upload.ts
14918
15240
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
14919
- import { z as z56 } from "zod";
14920
- var UploadSessionInputBase = z56.object({
14921
- mediaKind: z56.enum([
15241
+ import { z as z57 } from "zod";
15242
+ var UploadSessionInputBase = z57.object({
15243
+ mediaKind: z57.enum([
14922
15244
  "image",
14923
15245
  "video",
14924
15246
  "mixed"
14925
15247
  ]).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."),
14926
- assetRole: z56.enum([
15248
+ assetRole: z57.enum([
14927
15249
  "media",
14928
15250
  "thumbnail"
14929
15251
  ]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
14930
- uploadPurpose: z56.enum([
15252
+ uploadPurpose: z57.enum([
14931
15253
  "product_reference",
14932
15254
  "creative",
14933
15255
  "generic"
14934
15256
  ]).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."),
14935
- maxFiles: z56.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
15257
+ maxFiles: z57.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
14936
15258
  });
14937
- var OpenAiFileReference = z56.object({
14938
- 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."),
14939
- file_id: z56.string().min(1),
14940
- mime_type: z56.string().min(1).optional(),
14941
- file_name: z56.string().min(1).optional()
15259
+ var OpenAiFileReference = z57.object({
15260
+ download_url: z57.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
15261
+ file_id: z57.string().min(1),
15262
+ mime_type: z57.string().min(1).optional(),
15263
+ file_name: z57.string().min(1).optional()
14942
15264
  });
14943
15265
  var UploadSessionToolInputShape = {
14944
15266
  ...UploadSessionInputBase.shape,
14945
- 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.")
15267
+ files: z57.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.")
14946
15268
  };
14947
- var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
15269
+ var UploadSessionToolInput = z57.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
14948
15270
  if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
14949
15271
  ctx.addIssue({
14950
- code: z56.ZodIssueCode.custom,
15272
+ code: z57.ZodIssueCode.custom,
14951
15273
  path: [
14952
15274
  "mediaKind"
14953
15275
  ],
@@ -14956,7 +15278,7 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
14956
15278
  }
14957
15279
  if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
14958
15280
  ctx.addIssue({
14959
- code: z56.ZodIssueCode.custom,
15281
+ code: z57.ZodIssueCode.custom,
14960
15282
  path: [
14961
15283
  "maxFiles"
14962
15284
  ],
@@ -14965,7 +15287,7 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
14965
15287
  }
14966
15288
  if (value.mediaKind === "image" && (value.maxFiles ?? 1) > 5) {
14967
15289
  ctx.addIssue({
14968
- code: z56.ZodIssueCode.custom,
15290
+ code: z57.ZodIssueCode.custom,
14969
15291
  path: [
14970
15292
  "maxFiles"
14971
15293
  ],
@@ -14973,38 +15295,38 @@ var UploadSessionToolInput = z56.object(UploadSessionToolInputShape).superRefine
14973
15295
  });
14974
15296
  }
14975
15297
  });
14976
- var UploadSessionResponse = z56.object({
14977
- sessionId: z56.string().uuid(),
14978
- uploadPageUrl: z56.string().url(),
14979
- mediaKind: z56.enum([
15298
+ var UploadSessionResponse = z57.object({
15299
+ sessionId: z57.string().uuid(),
15300
+ uploadPageUrl: z57.string().url(),
15301
+ mediaKind: z57.enum([
14980
15302
  "image",
14981
15303
  "video",
14982
15304
  "mixed"
14983
15305
  ]),
14984
- assetRole: z56.enum([
15306
+ assetRole: z57.enum([
14985
15307
  "media",
14986
15308
  "thumbnail"
14987
15309
  ]),
14988
- maxFiles: z56.number().int().positive(),
14989
- expiresAt: z56.string().datetime()
15310
+ maxFiles: z57.number().int().positive(),
15311
+ expiresAt: z57.string().datetime()
14990
15312
  });
14991
- var UploadStatusInput = z56.object({
14992
- sessionId: z56.string().uuid().describe("The sessionId returned by request_media_upload.")
15313
+ var UploadStatusInput = z57.object({
15314
+ sessionId: z57.string().uuid().describe("The sessionId returned by request_media_upload.")
14993
15315
  });
14994
- var UniversalUploadStatusResponse = z56.discriminatedUnion("status", [
14995
- z56.object({
14996
- status: z56.literal("pending"),
14997
- assets: z56.array(z56.never()).length(0)
15316
+ var UniversalUploadStatusResponse = z57.discriminatedUnion("status", [
15317
+ z57.object({
15318
+ status: z57.literal("pending"),
15319
+ assets: z57.array(z57.never()).length(0)
14998
15320
  }),
14999
- z56.object({
15000
- status: z56.literal("uploaded"),
15001
- assets: z56.array(z56.object({
15002
- assetId: z56.string().uuid(),
15003
- mediaKind: z56.enum([
15321
+ z57.object({
15322
+ status: z57.literal("uploaded"),
15323
+ assets: z57.array(z57.object({
15324
+ assetId: z57.string().uuid(),
15325
+ mediaKind: z57.enum([
15004
15326
  "image",
15005
15327
  "video"
15006
15328
  ]),
15007
- assetRole: z56.enum([
15329
+ assetRole: z57.enum([
15008
15330
  "media",
15009
15331
  "thumbnail"
15010
15332
  ])
@@ -15012,13 +15334,13 @@ var UniversalUploadStatusResponse = z56.discriminatedUnion("status", [
15012
15334
  })
15013
15335
  ]);
15014
15336
  var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
15015
- uploadPurpose: z56.enum([
15337
+ uploadPurpose: z57.enum([
15016
15338
  "product_reference",
15017
15339
  "creative",
15018
15340
  "generic"
15019
15341
  ]),
15020
- checkToolName: z56.literal("check_media_upload"),
15021
- resolveEndpoint: z56.string().url()
15342
+ checkToolName: z57.literal("check_media_upload"),
15343
+ resolveEndpoint: z57.string().url()
15022
15344
  });
15023
15345
  function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
15024
15346
  const url = new URL(uploadPageUrl);
@@ -15052,6 +15374,7 @@ function registerMediaUploadTools(server, api) {
15052
15374
  title: "Subir archivos",
15053
15375
  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.",
15054
15376
  inputSchema: UploadSessionToolInputShape,
15377
+ annotations: CREATES_INTERNAL,
15055
15378
  outputSchema: UniversalUploadStructuredResponse.shape,
15056
15379
  _meta: {
15057
15380
  ui: {
@@ -15092,6 +15415,7 @@ function registerMediaUploadTools(server, api) {
15092
15415
  server.registerTool("check_media_upload", {
15093
15416
  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.",
15094
15417
  inputSchema: UploadStatusInput.shape,
15418
+ annotations: READS_INTERNAL,
15095
15419
  _meta: {
15096
15420
  "openai/widgetAccessible": true
15097
15421
  }
@@ -15107,16 +15431,16 @@ function registerMediaUploadTools(server, api) {
15107
15431
  __name(registerMediaUploadTools, "registerMediaUploadTools");
15108
15432
 
15109
15433
  // src/tools/campaigns.ts
15110
- import { z as z57 } from "zod";
15111
- var MetaStatusInput = z57.object({
15112
- campaignId: z57.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
15434
+ import { z as z58 } from "zod";
15435
+ var MetaStatusInput = z58.object({
15436
+ campaignId: z58.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
15113
15437
  });
15114
- var TikTokStatusInput = z57.object({
15115
- campaignId: z57.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
15116
- advertiserId: z57.string().min(1).describe("The same advertiserId used to publish the campaign.")
15438
+ var TikTokStatusInput = z58.object({
15439
+ campaignId: z58.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
15440
+ advertiserId: z58.string().min(1).describe("The same advertiserId used to publish the campaign.")
15117
15441
  });
15118
15442
  function registerCampaignTools(server, api) {
15119
- 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) => {
15443
+ 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) => {
15120
15444
  try {
15121
15445
  const r = await api.getJson(`/v1/facebook/campaigns/${encodeURIComponent(input.campaignId)}`);
15122
15446
  return renderMetaStatusMarkdown(r);
@@ -15124,7 +15448,7 @@ function registerCampaignTools(server, api) {
15124
15448
  throw translateError(e);
15125
15449
  }
15126
15450
  });
15127
- 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) => {
15451
+ 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) => {
15128
15452
  try {
15129
15453
  const r = await api.getJson(`/v1/tiktok/campaigns/${encodeURIComponent(input.campaignId)}?advertiserId=${encodeURIComponent(input.advertiserId)}`);
15130
15454
  return renderTikTokStatusMarkdown(r);
@@ -15210,10 +15534,10 @@ function renderTikTokStatusMarkdown(r) {
15210
15534
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
15211
15535
 
15212
15536
  // ../../packages/product-economics/src/product-costing.ts
15213
- import { z as z59 } from "zod";
15537
+ import { z as z60 } from "zod";
15214
15538
 
15215
15539
  // ../../packages/product-economics/src/product-costing-countries.ts
15216
- import { z as z58 } from "zod";
15540
+ import { z as z59 } from "zod";
15217
15541
 
15218
15542
  // ../../packages/product-economics/src/product-costing-shipping-references.json
15219
15543
  var product_costing_shipping_references_default = {
@@ -15277,21 +15601,21 @@ var product_costing_shipping_references_default = {
15277
15601
  };
15278
15602
 
15279
15603
  // ../../packages/product-economics/src/product-costing-countries.ts
15280
- var ShippingReferenceSchema = z58.object({
15281
- reference: z58.number().positive(),
15282
- min: z58.number().nonnegative(),
15283
- max: z58.number().positive(),
15284
- updatedAt: z58.string().date(),
15285
- confidence: z58.enum([
15604
+ var ShippingReferenceSchema = z59.object({
15605
+ reference: z59.number().positive(),
15606
+ min: z59.number().nonnegative(),
15607
+ max: z59.number().positive(),
15608
+ updatedAt: z59.string().date(),
15609
+ confidence: z59.enum([
15286
15610
  "high",
15287
15611
  "medium",
15288
15612
  "medium-low"
15289
15613
  ]),
15290
- sources: z58.array(z58.string().url().startsWith("https://")).min(1).max(3)
15614
+ sources: z59.array(z59.string().url().startsWith("https://")).min(1).max(3)
15291
15615
  }).superRefine(({ reference, min, max }, context) => {
15292
15616
  if (min > reference || reference > max) {
15293
15617
  context.addIssue({
15294
- code: z58.ZodIssueCode.custom,
15618
+ code: z59.ZodIssueCode.custom,
15295
15619
  message: "shipping_reference_outside_range",
15296
15620
  path: [
15297
15621
  "reference"
@@ -15299,7 +15623,7 @@ var ShippingReferenceSchema = z58.object({
15299
15623
  });
15300
15624
  }
15301
15625
  });
15302
- var SHIPPING_REFERENCES = z58.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
15626
+ var SHIPPING_REFERENCES = z59.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
15303
15627
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
15304
15628
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
15305
15629
  "MX",
@@ -15481,18 +15805,18 @@ function getProductCostingCountryName(country) {
15481
15805
  __name(getProductCostingCountryName, "getProductCostingCountryName");
15482
15806
 
15483
15807
  // ../../packages/product-economics/src/product-costing.ts
15484
- var ProductCostingInputSchema = z59.object({
15485
- country: z59.string().refine(isProductCostingCountryCode, "unsupported_country"),
15486
- supplierCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
15487
- shippingCost: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
15488
- returnRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
15489
- cancellationRate: z59.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
15490
- adsManagerCpa: z59.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
15491
- desiredMargin: z59.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
15808
+ var ProductCostingInputSchema = z60.object({
15809
+ country: z60.string().refine(isProductCostingCountryCode, "unsupported_country"),
15810
+ supplierCost: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
15811
+ shippingCost: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
15812
+ returnRate: z60.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
15813
+ cancellationRate: z60.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
15814
+ adsManagerCpa: z60.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
15815
+ desiredMargin: z60.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
15492
15816
  }).superRefine(({ returnRate, cancellationRate }, context) => {
15493
15817
  if (returnRate + cancellationRate >= 1) {
15494
15818
  context.addIssue({
15495
- code: z59.ZodIssueCode.custom,
15819
+ code: z60.ZodIssueCode.custom,
15496
15820
  message: "delivery_rate_positive",
15497
15821
  path: [
15498
15822
  "cancellationRate"
@@ -15559,14 +15883,14 @@ function calculateProductCosting(rawInput) {
15559
15883
  __name(calculateProductCosting, "calculateProductCosting");
15560
15884
 
15561
15885
  // src/tools/product-costing.ts
15562
- import { z as z60 } from "zod";
15563
- var optionalAmount = /* @__PURE__ */ __name((description) => z60.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
15564
- var optionalPercent = /* @__PURE__ */ __name((description) => z60.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
15565
- var ProductCostingToolInput = z60.object({
15566
- country: z60.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
15886
+ import { z as z61 } from "zod";
15887
+ var optionalAmount = /* @__PURE__ */ __name((description) => z61.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
15888
+ var optionalPercent = /* @__PURE__ */ __name((description) => z61.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
15889
+ var ProductCostingToolInput = z61.object({
15890
+ country: z61.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
15567
15891
  message: "Pa\xEDs no soportado por ProductMaker."
15568
15892
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
15569
- 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."),
15893
+ supplierCost: z61.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."),
15570
15894
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
15571
15895
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
15572
15896
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),
@@ -15577,7 +15901,7 @@ var ProductCostingToolInput = z60.object({
15577
15901
  desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
15578
15902
  });
15579
15903
  function registerProductCostingTools(server) {
15580
- 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) => {
15904
+ 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) => {
15581
15905
  if (input.country !== void 0 && input.country !== "CO" && [
15582
15906
  input.supplierCostCop,
15583
15907
  input.shippingCostCop,
@@ -15698,7 +16022,7 @@ function formatPercent(value) {
15698
16022
  __name(formatPercent, "formatPercent");
15699
16023
 
15700
16024
  // src/tools/product-research.ts
15701
- import { z as z61 } from "zod";
16025
+ import { z as z62 } from "zod";
15702
16026
 
15703
16027
  // src/tools/product-research-idempotency.ts
15704
16028
  import { createHash as createHash3 } from "crypto";
@@ -15814,17 +16138,17 @@ var TERMINAL2 = /* @__PURE__ */ new Set([
15814
16138
  ]);
15815
16139
  var POLL_INTERVAL_MS = 5e3;
15816
16140
  var MAX_WAIT_SECONDS = 120;
15817
- var FindInput = z61.object({
16141
+ var FindInput = z62.object({
15818
16142
  targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
15819
16143
  categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
15820
- 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.`),
15821
- 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.")
16144
+ confirmed: z62.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
16145
+ waitSeconds: z62.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
15822
16146
  });
15823
- var GetInput = z61.object({
15824
- runId: z61.string().uuid().describe("ID del run devuelto por find_winning_products.")
16147
+ var GetInput = z62.object({
16148
+ runId: z62.string().uuid().describe("ID del run devuelto por find_winning_products.")
15825
16149
  });
15826
- var ListInput = z61.object({
15827
- limit: z61.number().int().min(1).max(20).default(10)
16150
+ var ListInput = z62.object({
16151
+ limit: z62.number().int().min(1).max(20).default(10)
15828
16152
  });
15829
16153
  function textResult(text) {
15830
16154
  return {
@@ -15849,7 +16173,7 @@ async function pollUntilTerminal(api, runId, waitSeconds) {
15849
16173
  }
15850
16174
  __name(pollUntilTerminal, "pollUntilTerminal");
15851
16175
  function registerProductResearchTools(server, api) {
15852
- 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) => {
16176
+ 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) => {
15853
16177
  const parsed = FindInput.parse(input);
15854
16178
  if (!parsed.confirmed) {
15855
16179
  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.`);
@@ -15883,7 +16207,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
15883
16207
  throw translateError(error);
15884
16208
  }
15885
16209
  });
15886
- 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) => {
16210
+ 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) => {
15887
16211
  const parsed = GetInput.parse(input);
15888
16212
  try {
15889
16213
  const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
@@ -15903,7 +16227,7 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
15903
16227
  throw translateError(error);
15904
16228
  }
15905
16229
  });
15906
- 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) => {
16230
+ 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) => {
15907
16231
  const parsed = ListInput.parse(input);
15908
16232
  try {
15909
16233
  const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
@@ -15916,14 +16240,14 @@ _Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reci
15916
16240
  __name(registerProductResearchTools, "registerProductResearchTools");
15917
16241
 
15918
16242
  // src/tools/dropshipping-projects.ts
15919
- import { z as z62 } from "zod";
15920
- var ProjectId = z62.string().uuid();
15921
- var DropshippingProjectStatus = z62.enum([
16243
+ import { z as z63 } from "zod";
16244
+ var ProjectId = z63.string().uuid();
16245
+ var DropshippingProjectStatus = z63.enum([
15922
16246
  "active",
15923
16247
  "completed",
15924
16248
  "archived"
15925
16249
  ]);
15926
- var DropshippingProjectPhase = z62.enum([
16250
+ var DropshippingProjectPhase = z63.enum([
15927
16251
  "research",
15928
16252
  "costing",
15929
16253
  "media",
@@ -15932,21 +16256,21 @@ var DropshippingProjectPhase = z62.enum([
15932
16256
  "advertising",
15933
16257
  "complete"
15934
16258
  ]);
15935
- var DropshippingRewardMilestone = z62.enum([
16259
+ var DropshippingRewardMilestone = z63.enum([
15936
16260
  "product_selected",
15937
16261
  "costing_saved",
15938
16262
  "product_media_ready",
15939
16263
  "creatives_completed",
15940
16264
  "campaign_created"
15941
16265
  ]);
15942
- var TargetCountry = z62.enum([
16266
+ var TargetCountry = z63.enum([
15943
16267
  "CO",
15944
16268
  "MX",
15945
16269
  "PE",
15946
16270
  "EC",
15947
16271
  "CL"
15948
16272
  ]);
15949
- var ResearchCategory = z62.enum([
16273
+ var ResearchCategory = z63.enum([
15950
16274
  "auto",
15951
16275
  "automotive",
15952
16276
  "kitchen",
@@ -15961,7 +16285,7 @@ var ResearchCategory = z62.enum([
15961
16285
  "garden",
15962
16286
  "cleaning"
15963
16287
  ]);
15964
- var HttpUrl3 = z62.string().url().max(2e3).refine((value) => {
16288
+ var HttpUrl3 = z63.string().url().max(2e3).refine((value) => {
15965
16289
  try {
15966
16290
  return [
15967
16291
  "http:",
@@ -15971,43 +16295,43 @@ var HttpUrl3 = z62.string().url().max(2e3).refine((value) => {
15971
16295
  return false;
15972
16296
  }
15973
16297
  }, "URL must use HTTP(S)");
15974
- var ResearchCandidateOrigin = z62.object({
15975
- type: z62.literal("research_candidate"),
16298
+ var ResearchCandidateOrigin = z63.object({
16299
+ type: z63.literal("research_candidate"),
15976
16300
  runId: ProjectId,
15977
- candidateSourceId: z62.string().min(1).max(160)
16301
+ candidateSourceId: z63.string().min(1).max(160)
15978
16302
  });
15979
- var TaskOrigin = z62.object({
15980
- type: z62.literal("task"),
16303
+ var TaskOrigin = z63.object({
16304
+ type: z63.literal("task"),
15981
16305
  taskId: ProjectId
15982
16306
  });
15983
- var ManualOrigin = z62.object({
15984
- type: z62.literal("manual"),
16307
+ var ManualOrigin = z63.object({
16308
+ type: z63.literal("manual"),
15985
16309
  clientRequestId: ProjectId,
15986
- country: z62.string().min(2).max(3).optional()
16310
+ country: z63.string().min(2).max(3).optional()
15987
16311
  });
15988
- var StartProjectInput = z62.object({
15989
- origin: z62.discriminatedUnion("type", [
16312
+ var StartProjectInput = z63.object({
16313
+ origin: z63.discriminatedUnion("type", [
15990
16314
  ResearchCandidateOrigin,
15991
16315
  TaskOrigin,
15992
16316
  ManualOrigin
15993
16317
  ]),
15994
- productName: z62.string().trim().min(1).max(240).optional(),
16318
+ productName: z63.string().trim().min(1).max(240).optional(),
15995
16319
  productUrl: HttpUrl3.optional(),
15996
- country: z62.string().min(2).max(3).optional(),
15997
- languageCode: z62.string().min(2).max(16).optional()
16320
+ country: z63.string().min(2).max(3).optional(),
16321
+ languageCode: z63.string().min(2).max(16).optional()
15998
16322
  }).strict();
15999
- var CostProjectInput = z62.object({
16323
+ var CostProjectInput = z63.object({
16000
16324
  projectId: ProjectId,
16001
- version: z62.number().int().positive(),
16002
- country: z62.string().trim().min(2).max(3),
16003
- supplierCost: z62.number().finite().positive(),
16004
- shippingCost: z62.number().finite().nonnegative(),
16005
- returnRate: z62.number().finite().min(0).lt(1),
16006
- cancellationRate: z62.number().finite().min(0).lt(1),
16007
- adsManagerCpa: z62.number().finite().nonnegative(),
16008
- desiredMargin: z62.number().finite().min(0).lt(1)
16009
- });
16010
- var GuideInput = z62.object({
16325
+ version: z63.number().int().positive(),
16326
+ country: z63.string().trim().min(2).max(3),
16327
+ supplierCost: z63.number().finite().positive(),
16328
+ shippingCost: z63.number().finite().nonnegative(),
16329
+ returnRate: z63.number().finite().min(0).lt(1),
16330
+ cancellationRate: z63.number().finite().min(0).lt(1),
16331
+ adsManagerCpa: z63.number().finite().nonnegative(),
16332
+ desiredMargin: z63.number().finite().min(0).lt(1)
16333
+ });
16334
+ var GuideInput = z63.object({
16011
16335
  projectId: ProjectId.optional()
16012
16336
  }).strict();
16013
16337
  var NEXT_ACTION_KINDS = [
@@ -16025,29 +16349,29 @@ var NEXT_ACTION_KINDS = [
16025
16349
  "repair_meta_campaign",
16026
16350
  "review_paused_campaign"
16027
16351
  ];
16028
- var DropshippingProjectNextAction = z62.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z62.object({
16029
- kind: z62.literal(kind),
16030
- href: z62.string().min(1)
16352
+ var DropshippingProjectNextAction = z63.discriminatedUnion("kind", NEXT_ACTION_KINDS.map((kind) => z63.object({
16353
+ kind: z63.literal(kind),
16354
+ href: z63.string().min(1)
16031
16355
  })));
16032
- var CostingInputSchema = z62.object({
16033
- country: z62.string().min(2).max(3),
16034
- supplierCost: z62.number().finite().positive(),
16035
- shippingCost: z62.number().finite().nonnegative(),
16036
- returnRate: z62.number().finite().min(0).lt(1),
16037
- cancellationRate: z62.number().finite().min(0).lt(1),
16038
- adsManagerCpa: z62.number().finite().nonnegative(),
16039
- desiredMargin: z62.number().finite().min(0).lt(1)
16356
+ var CostingInputSchema = z63.object({
16357
+ country: z63.string().min(2).max(3),
16358
+ supplierCost: z63.number().finite().positive(),
16359
+ shippingCost: z63.number().finite().nonnegative(),
16360
+ returnRate: z63.number().finite().min(0).lt(1),
16361
+ cancellationRate: z63.number().finite().min(0).lt(1),
16362
+ adsManagerCpa: z63.number().finite().nonnegative(),
16363
+ desiredMargin: z63.number().finite().min(0).lt(1)
16040
16364
  }).superRefine(({ returnRate, cancellationRate }, context) => {
16041
16365
  if (returnRate + cancellationRate >= 1) context.addIssue({
16042
- code: z62.ZodIssueCode.custom,
16366
+ code: z63.ZodIssueCode.custom,
16043
16367
  path: [
16044
16368
  "cancellationRate"
16045
16369
  ]
16046
16370
  });
16047
16371
  });
16048
- var FiniteCostingNumber = z62.number().finite();
16049
- var CostingResultSchema = z62.object({
16050
- country: z62.string().min(2).max(3),
16372
+ var FiniteCostingNumber = z63.number().finite();
16373
+ var CostingResultSchema = z63.object({
16374
+ country: z63.string().min(2).max(3),
16051
16375
  deliveryRate: FiniteCostingNumber,
16052
16376
  adjustedShippingCost: FiniteCostingNumber,
16053
16377
  adjustedCpa: FiniteCostingNumber,
@@ -16060,44 +16384,44 @@ var CostingResultSchema = z62.object({
16060
16384
  estimatedProfit: FiniteCostingNumber,
16061
16385
  actualMargin: FiniteCostingNumber,
16062
16386
  breakEvenAdsManagerCpa: FiniteCostingNumber,
16063
- wasPriceAutoAdjusted: z62.boolean(),
16064
- profitabilityStatus: z62.literal("healthy")
16065
- });
16066
- var RewardProgressSchema = z62.object({
16067
- campaignKey: z62.literal("dropshipping-launch-v1"),
16068
- totalCredits: z62.number().int().positive(),
16069
- earnedCredits: z62.number().int().nonnegative(),
16070
- milestones: z62.array(z62.object({
16387
+ wasPriceAutoAdjusted: z63.boolean(),
16388
+ profitabilityStatus: z63.literal("healthy")
16389
+ });
16390
+ var RewardProgressSchema = z63.object({
16391
+ campaignKey: z63.literal("dropshipping-launch-v1"),
16392
+ totalCredits: z63.number().int().positive(),
16393
+ earnedCredits: z63.number().int().nonnegative(),
16394
+ milestones: z63.array(z63.object({
16071
16395
  milestone: DropshippingRewardMilestone,
16072
- credits: z62.number().int().positive(),
16073
- available: z62.boolean().optional(),
16074
- earned: z62.boolean(),
16396
+ credits: z63.number().int().positive(),
16397
+ available: z63.boolean().optional(),
16398
+ earned: z63.boolean(),
16075
16399
  grantId: ProjectId.nullable(),
16076
- grantedAt: z62.string().datetime().nullable()
16400
+ grantedAt: z63.string().datetime().nullable()
16077
16401
  })).length(DropshippingRewardMilestone.options.length)
16078
16402
  });
16079
- var ProjectResponseSchema = z62.object({
16403
+ var ProjectResponseSchema = z63.object({
16080
16404
  id: ProjectId,
16081
16405
  taskId: ProjectId.nullable(),
16082
16406
  status: DropshippingProjectStatus,
16083
- originKey: z62.string().min(1),
16407
+ originKey: z63.string().min(1),
16084
16408
  researchRunId: ProjectId.nullable(),
16085
- candidateSourceId: z62.string().nullable(),
16086
- productName: z62.string().nullable(),
16409
+ candidateSourceId: z63.string().nullable(),
16410
+ productName: z63.string().nullable(),
16087
16411
  productUrl: HttpUrl3.nullable(),
16088
16412
  referenceImageUrl: HttpUrl3.nullable(),
16089
- targetCountry: z62.string().nullable(),
16090
- languageCode: z62.string().nullable(),
16091
- hasReferenceImage: z62.boolean(),
16413
+ targetCountry: z63.string().nullable(),
16414
+ languageCode: z63.string().nullable(),
16415
+ hasReferenceImage: z63.boolean(),
16092
16416
  costingInput: CostingInputSchema.nullable(),
16093
16417
  costingResult: CostingResultSchema.nullable(),
16094
- costingVersion: z62.number().int().positive().nullable(),
16095
- version: z62.number().int().positive(),
16418
+ costingVersion: z63.number().int().positive().nullable(),
16419
+ version: z63.number().int().positive(),
16096
16420
  phase: DropshippingProjectPhase,
16097
16421
  nextAction: DropshippingProjectNextAction.nullable(),
16098
16422
  rewards: RewardProgressSchema,
16099
- createdAt: z62.string().datetime(),
16100
- updatedAt: z62.string().datetime()
16423
+ createdAt: z63.string().datetime(),
16424
+ updatedAt: z63.string().datetime()
16101
16425
  }).passthrough();
16102
16426
  var NEXT_ACTION_GUIDANCE = {
16103
16427
  cost_product: "Llama `cost_dropshipping_project` con el costo real del proveedor. Nunca lo inventes: p\xEDdeselo al usuario.",
@@ -16146,28 +16470,38 @@ function projectText(raw) {
16146
16470
  }
16147
16471
  __name(projectText, "projectText");
16148
16472
  function registerDropshippingProjectTools(server, api) {
16149
- 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) => {
16473
+ 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) => {
16150
16474
  try {
16151
16475
  return projectText(await api.postJson("/v1/dropshipping-projects", input));
16152
16476
  } catch (error) {
16153
16477
  throw translateError(error);
16154
16478
  }
16155
16479
  });
16156
- 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) => {
16157
- try {
16158
- if (input.returnRate + input.cancellationRate >= 1) {
16159
- throw new Error("returnRate + cancellationRate debe ser menor que 1.");
16480
+ server.tool(
16481
+ "cost_dropshipping_project",
16482
+ "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.",
16483
+ CostProjectInput.shape,
16484
+ // Overwrites the costing inputs already saved on the project — the same
16485
+ // "previous values are gone" case as edit_task_draft, so destructive.
16486
+ // idempotentHint stays false despite the PUT: the `version` guard makes a
16487
+ // blind retry FAIL rather than replay, so a host should not auto-retry it.
16488
+ OVERWRITES_INTERNAL,
16489
+ async (input) => {
16490
+ try {
16491
+ if (input.returnRate + input.cancellationRate >= 1) {
16492
+ throw new Error("returnRate + cancellationRate debe ser menor que 1.");
16493
+ }
16494
+ const { projectId, version, ...costingInput } = input;
16495
+ return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
16496
+ version,
16497
+ input: costingInput
16498
+ }));
16499
+ } catch (error) {
16500
+ throw translateError(error);
16160
16501
  }
16161
- const { projectId, version, ...costingInput } = input;
16162
- return projectText(await api.putJson(`/v1/dropshipping-projects/${encodeURIComponent(projectId)}/costing`, {
16163
- version,
16164
- input: costingInput
16165
- }));
16166
- } catch (error) {
16167
- throw translateError(error);
16168
16502
  }
16169
- });
16170
- 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) => {
16503
+ );
16504
+ 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) => {
16171
16505
  try {
16172
16506
  if (!input.projectId) {
16173
16507
  return asText({
@@ -16518,6 +16852,14 @@ function createApp(opts) {
16518
16852
  ok: true
16519
16853
  });
16520
16854
  });
16855
+ app.get("/.well-known/openai-apps-challenge", (_req, res) => {
16856
+ const token = opts.appsChallengeToken?.trim();
16857
+ if (!token) {
16858
+ res.status(404).type("text/plain").send("Not found");
16859
+ return;
16860
+ }
16861
+ res.status(200).type("text/plain").send(token);
16862
+ });
16521
16863
  app.get("/assets/:token/:filename?", rateLimit({
16522
16864
  windowMs: 6e4,
16523
16865
  max: 600,
@@ -16589,7 +16931,8 @@ async function startHttpServer(env = process.env) {
16589
16931
  const app = createApp({
16590
16932
  apiBaseUrl,
16591
16933
  proxyBaseUrl,
16592
- oauth
16934
+ oauth,
16935
+ appsChallengeToken: env.OPENAI_APPS_CHALLENGE_TOKEN ?? null
16593
16936
  });
16594
16937
  return await new Promise((resolve3, reject) => {
16595
16938
  const server = app.listen(port, () => {