@productmaker/mcp 6.2.0 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +217 -25
- package/dist/stdio.js +217 -25
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -619,7 +619,25 @@ var AdaptiveProductReferenceStateSchema = z5.object({
|
|
|
619
619
|
* may enrich the candidate later without changing the photos the user
|
|
620
620
|
* approved; that must not invalidate the answer and reopen the question.
|
|
621
621
|
*/
|
|
622
|
-
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional()
|
|
622
|
+
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional(),
|
|
623
|
+
/**
|
|
624
|
+
* SHE PRESSED "SIGUE CON ESTAS FOTOS".
|
|
625
|
+
*
|
|
626
|
+
* The evidence version she pressed it at. The guard is
|
|
627
|
+
* `keptSelectionAtEvidenceVersion <= referenceEvidenceVersion`, so the
|
|
628
|
+
* retraction SURVIVES a later photo change — exactly like every other answer
|
|
629
|
+
* since answers stopped being filtered by evidence version. Recording the
|
|
630
|
+
* version is what makes a retraction taken at a FUTURE version (impossible
|
|
631
|
+
* today, but the ordering is asserted rather than assumed) not count.
|
|
632
|
+
*
|
|
633
|
+
* This is a RETRACTION, and it needs its own field because it cannot be
|
|
634
|
+
* expressed as an answer: the `different_products` answer that opened visual
|
|
635
|
+
* recovery short-circuits `buildAdaptiveProductReferenceDecision` before any
|
|
636
|
+
* later answer is read, so recording one more answer could never close the
|
|
637
|
+
* gate. Bumping the evidence version instead would drop every answer and
|
|
638
|
+
* restart the interview from `sale_structure` — the opposite of "continue".
|
|
639
|
+
*/
|
|
640
|
+
keptSelectionAtEvidenceVersion: z5.number().int().positive().optional()
|
|
623
641
|
});
|
|
624
642
|
var AdaptiveProductReferenceQuestionOptionSchema = z5.object({
|
|
625
643
|
value: ProductReferenceUserChoiceSchema,
|
|
@@ -3078,7 +3096,8 @@ var ShopifyPublishResponse = z22.object({
|
|
|
3078
3096
|
var ShopifyPublishErrorResponse = z22.object({
|
|
3079
3097
|
code: z22.enum([
|
|
3080
3098
|
"SHOPIFY_NOT_CONNECTED",
|
|
3081
|
-
"SHOPIFY_PUBLISH_FAILED"
|
|
3099
|
+
"SHOPIFY_PUBLISH_FAILED",
|
|
3100
|
+
"SHOPIFY_NO_PUBLISHED_THEME"
|
|
3082
3101
|
]),
|
|
3083
3102
|
message: z22.string().optional(),
|
|
3084
3103
|
authUrl: z22.string().url().optional(),
|
|
@@ -3220,9 +3239,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3220
3239
|
referenceEvidenceVersion: z23.number().int().positive().optional(),
|
|
3221
3240
|
expectedEvidenceHash: z23.string().min(1).max(128).optional(),
|
|
3222
3241
|
recovery: z23.object({
|
|
3242
|
+
/**
|
|
3243
|
+
* `keep_selection` is NOT a recovery in the billing sense: it commits
|
|
3244
|
+
* no new evidence, re-enqueues no extract, and costs nothing. It rides
|
|
3245
|
+
* on this field because it answers the same card, and the server
|
|
3246
|
+
* branches on it BEFORE the billed recovery path.
|
|
3247
|
+
*/
|
|
3223
3248
|
action: z23.enum([
|
|
3224
3249
|
"replace_selection",
|
|
3225
|
-
"refine_selection"
|
|
3250
|
+
"refine_selection",
|
|
3251
|
+
"keep_selection"
|
|
3226
3252
|
]),
|
|
3227
3253
|
affectedRefKeys: z23.array(z23.string().min(1).max(200)).min(1).max(5).optional(),
|
|
3228
3254
|
expectedReferenceVersionId: z23.string().uuid().optional(),
|
|
@@ -3230,6 +3256,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3230
3256
|
uploadedImages: z23.array(ProductReferenceRecoveryUpload).max(INGEST_MAX_REFERENCE_IMAGES).optional()
|
|
3231
3257
|
}).optional()
|
|
3232
3258
|
}).superRefine((value, context) => {
|
|
3259
|
+
if (value.recovery?.action === "keep_selection" && value.recovery.uploadedImages?.length) {
|
|
3260
|
+
context.addIssue({
|
|
3261
|
+
code: z23.ZodIssueCode.custom,
|
|
3262
|
+
path: [
|
|
3263
|
+
"recovery",
|
|
3264
|
+
"uploadedImages"
|
|
3265
|
+
],
|
|
3266
|
+
message: "keep_selection_does_not_accept_uploads"
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3233
3269
|
if (value.recovery?.action === "refine_selection" && value.recovery.uploadedImages?.length) {
|
|
3234
3270
|
context.addIssue({
|
|
3235
3271
|
code: z23.ZodIssueCode.custom,
|
|
@@ -3797,6 +3833,16 @@ var PendingUserActionSchema = z26.discriminatedUnion("kind", [
|
|
|
3797
3833
|
referenceEvidenceVersion: z26.number().int().positive().optional(),
|
|
3798
3834
|
expectedEvidenceHash: z26.string().min(1).max(128).optional(),
|
|
3799
3835
|
question: AdaptiveProductReferenceQuestionSchema.optional(),
|
|
3836
|
+
/**
|
|
3837
|
+
* TRUE when "sigue con estas fotos" is a real exit from this card.
|
|
3838
|
+
*
|
|
3839
|
+
* Absent means the seller genuinely has to supply pixels we do not have,
|
|
3840
|
+
* and offering to continue would be a lie: nothing is selected at all, or
|
|
3841
|
+
* a combo component was never photographed. Every client reads this — web,
|
|
3842
|
+
* MCP and WhatsApp — so no surface can invent an exit the server did not
|
|
3843
|
+
* offer, nor omit one it did.
|
|
3844
|
+
*/
|
|
3845
|
+
canKeepSelection: z26.boolean().optional(),
|
|
3800
3846
|
recovery: z26.object({
|
|
3801
3847
|
kind: z26.literal("combo_component_evidence"),
|
|
3802
3848
|
minimumImageCount: z26.number().int().min(2).max(5),
|
|
@@ -3973,6 +4019,28 @@ var StatusResponse = z26.object({
|
|
|
3973
4019
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
3974
4020
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
3975
4021
|
hasAnswerableGate: z26.boolean().optional(),
|
|
4022
|
+
/**
|
|
4023
|
+
* "Is the ANGLE gate — and only that gate — still open?"
|
|
4024
|
+
*
|
|
4025
|
+
* `needsReview` is an OR over every gate (reference, angles, SCRIPT review,
|
|
4026
|
+
* grouping). The chat used to read that OR as proof that the server was
|
|
4027
|
+
* waiting on ANGLES, because the two conditions it paired it with
|
|
4028
|
+
* (`stageStatuses.video_generator === 'pending'` + `status === 'processing'`)
|
|
4029
|
+
* are ALSO true throughout script review. So the moment a script became
|
|
4030
|
+
* ready — a gate the seller had already passed the angle card to reach — the
|
|
4031
|
+
* chat "recovered" the angle review card over a run that was already
|
|
4032
|
+
* dispatched, released its own submit guard, and re-enabled «Generar». The
|
|
4033
|
+
* seller pressed it again; the second resume lost the compare-and-clear and
|
|
4034
|
+
* answered 201 with a plain status body, so the no-op read as success and the
|
|
4035
|
+
* loop repeated (measured 2026-08-27: three resumes in 16s, two of them
|
|
4036
|
+
* no-ops).
|
|
4037
|
+
*
|
|
4038
|
+
* Named rather than inferred, for the same reason as `hasAnswerableGate`: a
|
|
4039
|
+
* gate added tomorrow widens `needsReview` and would silently re-arm that
|
|
4040
|
+
* recovery path. Absent (undefined) means "an older API that cannot answer" —
|
|
4041
|
+
* clients must fall back to `pauseForAngleReview` alone, never to the OR.
|
|
4042
|
+
*/
|
|
4043
|
+
awaitingAngleReview: z26.boolean().optional(),
|
|
3976
4044
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
3977
4045
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
3978
4046
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -10516,6 +10584,10 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10516
10584
|
defineCapability("cost_product", "6d1f85a999dbdabae4eda406ff4689cdff2016df375b5b6741b91f4308d819af", "none", "read_only", [
|
|
10517
10585
|
"read"
|
|
10518
10586
|
], [
|
|
10587
|
+
slot("product_reference", false, [], [
|
|
10588
|
+
"selectedProductTitle",
|
|
10589
|
+
"selectedProductUrl"
|
|
10590
|
+
], "server"),
|
|
10519
10591
|
slot("market", true, [
|
|
10520
10592
|
"country"
|
|
10521
10593
|
]),
|
|
@@ -10586,7 +10658,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10586
10658
|
], [
|
|
10587
10659
|
"creative_variant"
|
|
10588
10660
|
]),
|
|
10589
|
-
defineCapability("create_product_task", "
|
|
10661
|
+
defineCapability("create_product_task", "13a037c7365cc4c30d81a1d89515e663a3de92ffd4138a117fa32ecb629bca44", "credit_consuming", "creative", [
|
|
10590
10662
|
"credit_consuming",
|
|
10591
10663
|
"state_change"
|
|
10592
10664
|
], [
|
|
@@ -10817,7 +10889,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10817
10889
|
"images"
|
|
10818
10890
|
], [
|
|
10819
10891
|
"imageMessageIds",
|
|
10820
|
-
"projectId"
|
|
10892
|
+
"projectId",
|
|
10893
|
+
"productUrl"
|
|
10821
10894
|
]),
|
|
10822
10895
|
slot("product_identity", true, [
|
|
10823
10896
|
"productTitle"
|
|
@@ -10889,7 +10962,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10889
10962
|
"images"
|
|
10890
10963
|
], [
|
|
10891
10964
|
"imageMessageIds",
|
|
10892
|
-
"projectId"
|
|
10965
|
+
"projectId",
|
|
10966
|
+
"productUrl"
|
|
10893
10967
|
]),
|
|
10894
10968
|
slot("locale", true, [
|
|
10895
10969
|
"language",
|
|
@@ -10956,6 +11030,15 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10956
11030
|
explicitOmissionAllowed: false,
|
|
10957
11031
|
fieldCompletion: "all"
|
|
10958
11032
|
},
|
|
11033
|
+
{
|
|
11034
|
+
slot: "sales_context",
|
|
11035
|
+
askBeforePaidExecution: true,
|
|
11036
|
+
explicitOmissionAllowed: false,
|
|
11037
|
+
fieldCompletion: "any",
|
|
11038
|
+
completionFields: [
|
|
11039
|
+
"salesAngle"
|
|
11040
|
+
]
|
|
11041
|
+
},
|
|
10959
11042
|
{
|
|
10960
11043
|
slot: "commercial_offer",
|
|
10961
11044
|
askBeforePaidExecution: true,
|
|
@@ -10971,7 +11054,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10971
11054
|
"images"
|
|
10972
11055
|
], [
|
|
10973
11056
|
"imageMessageIds",
|
|
10974
|
-
"projectId"
|
|
11057
|
+
"projectId",
|
|
11058
|
+
"productUrl"
|
|
10975
11059
|
]),
|
|
10976
11060
|
slot("product_identity", true, [
|
|
10977
11061
|
"title"
|
|
@@ -11024,6 +11108,25 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11024
11108
|
"product_reference"
|
|
11025
11109
|
], [
|
|
11026
11110
|
"video"
|
|
11111
|
+
], SHARED_CHANNEL_EXPOSURE, [
|
|
11112
|
+
{
|
|
11113
|
+
slot: "product_reference",
|
|
11114
|
+
askBeforePaidExecution: true,
|
|
11115
|
+
explicitOmissionAllowed: false,
|
|
11116
|
+
fieldCompletion: "any"
|
|
11117
|
+
},
|
|
11118
|
+
{
|
|
11119
|
+
slot: "locale",
|
|
11120
|
+
askBeforePaidExecution: true,
|
|
11121
|
+
explicitOmissionAllowed: false,
|
|
11122
|
+
fieldCompletion: "all"
|
|
11123
|
+
},
|
|
11124
|
+
{
|
|
11125
|
+
slot: "commercial_offer",
|
|
11126
|
+
askBeforePaidExecution: true,
|
|
11127
|
+
explicitOmissionAllowed: true,
|
|
11128
|
+
fieldCompletion: "all"
|
|
11129
|
+
}
|
|
11027
11130
|
]),
|
|
11028
11131
|
defineCapability("get_meta_campaign_status", "92077241cf4e9d14edefca410e85715cc383b1433081c3461c925f8fc3d6e8ab", "none", "read_only", [
|
|
11029
11132
|
"read"
|
|
@@ -11908,6 +12011,10 @@ function parsePublicHttpsOrigin(value) {
|
|
|
11908
12011
|
}
|
|
11909
12012
|
__name(parsePublicHttpsOrigin, "parsePublicHttpsOrigin");
|
|
11910
12013
|
|
|
12014
|
+
// ../../packages/types/src/whatsapp.ts
|
|
12015
|
+
var WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
|
|
12016
|
+
var WHATSAPP_MAX_AUDIO_DURATION_SECONDS = 10 * 60;
|
|
12017
|
+
|
|
11911
12018
|
// src/http.ts
|
|
11912
12019
|
import express from "express";
|
|
11913
12020
|
import rateLimit from "express-rate-limit";
|
|
@@ -13167,7 +13274,7 @@ var ApiClient = class {
|
|
|
13167
13274
|
};
|
|
13168
13275
|
|
|
13169
13276
|
// src/index.ts
|
|
13170
|
-
var MCP_VERSION = "6.
|
|
13277
|
+
var MCP_VERSION = "6.3.1";
|
|
13171
13278
|
|
|
13172
13279
|
// src/meta-upload-app.ts
|
|
13173
13280
|
import { readFileSync } from "fs";
|
|
@@ -13520,6 +13627,35 @@ function sleep(ms, signal) {
|
|
|
13520
13627
|
}
|
|
13521
13628
|
__name(sleep, "sleep");
|
|
13522
13629
|
|
|
13630
|
+
// src/polling-continuation.ts
|
|
13631
|
+
var NO_CONTINUATION_STATUSES = /* @__PURE__ */ new Set([
|
|
13632
|
+
"done",
|
|
13633
|
+
"complete",
|
|
13634
|
+
"error",
|
|
13635
|
+
"awaiting_review"
|
|
13636
|
+
]);
|
|
13637
|
+
var SUGGESTED_WAIT_SECONDS = 45;
|
|
13638
|
+
function buildPollingContinuation(snapshot, opts = {}) {
|
|
13639
|
+
if (NO_CONTINUATION_STATUSES.has(snapshot.status)) return null;
|
|
13640
|
+
if (snapshot.pendingUserAction != null) return null;
|
|
13641
|
+
const suggestedWaitSeconds = opts.suggestedWaitSeconds ?? SUGGESTED_WAIT_SECONDS;
|
|
13642
|
+
return {
|
|
13643
|
+
stillRunning: true,
|
|
13644
|
+
nextAction: "continue_polling_get_task_status",
|
|
13645
|
+
suggestedWaitSeconds,
|
|
13646
|
+
instruction: `La tarea SIGUE CORRIENDO: no termin\xF3, no fall\xF3 y todav\xEDa no hay nada que el usuario deba revisar. Esto NO es un error ni un final de trabajo.
|
|
13647
|
+
|
|
13648
|
+
REGLA: no termines tu turno sin dejar ARMADA la siguiente consulta. Cerrar el turno est\xE1 bien; cerrarlo sin nada que vuelva a preguntar es lo que deja al usuario esperando para siempre una revisi\xF3n que nunca le vas a mostrar.
|
|
13649
|
+
|
|
13650
|
+
Elige seg\xFAn lo que tu host permita:
|
|
13651
|
+
1. PREFERIDO \u2014 si puedes agendar un mensaje o turno futuro (mensajes programados, recordatorios): agenda uno a ~2 minutos que vuelva a llamar get_task_status con este mismo taskId, dile al usuario que le avisas, y ah\xED s\xED cierra el turno. Es la mejor opci\xF3n: el usuario queda libre y t\xFA vuelves solo.
|
|
13652
|
+
2. SI NO PUEDES AGENDAR: llama otra vez a get_task_status AHORA MISMO con waitSeconds=${suggestedWaitSeconds}, y repite hasta un estado terminal (done/error) o una acci\xF3n pendiente del usuario. NO cierres el turno con un "te aviso cuando est\xE9": nadie te va a volver a despertar.
|
|
13653
|
+
|
|
13654
|
+
No subas waitSeconds por encima de ${suggestedWaitSeconds} salvo que sepas que tu host aguanta llamadas m\xE1s largas: muchos clientes MCP cancelan a los 60 s y ver\xEDas un error de timeout en vez del estado.`
|
|
13655
|
+
};
|
|
13656
|
+
}
|
|
13657
|
+
__name(buildPollingContinuation, "buildPollingContinuation");
|
|
13658
|
+
|
|
13523
13659
|
// src/errors.ts
|
|
13524
13660
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
13525
13661
|
|
|
@@ -14237,6 +14373,7 @@ var McpErrorCode = {
|
|
|
14237
14373
|
SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
|
|
14238
14374
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
|
|
14239
14375
|
SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
|
|
14376
|
+
SHOPIFY_NO_PUBLISHED_THEME: "SHOPIFY_NO_PUBLISHED_THEME",
|
|
14240
14377
|
// Meta / Facebook Ads
|
|
14241
14378
|
META_NO_FB_PAGE: "META_NO_FB_PAGE",
|
|
14242
14379
|
META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
|
|
@@ -14304,6 +14441,7 @@ var CODE_MESSAGES = {
|
|
|
14304
14441
|
SHOPIFY_TOKEN_EXPIRED: "The Shopify session expired. Reconnect the store at https://productmaker.app/integrations.",
|
|
14305
14442
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "The Shopify connection needs additional scopes. Reconnect it at https://productmaker.app/integrations to approve the new permissions.",
|
|
14306
14443
|
SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
|
|
14444
|
+
SHOPIFY_NO_PUBLISHED_THEME: "The Shopify store has no published theme, so there is nowhere to place the landing. Ask the user to publish a theme (Online Store > Themes > Publish) and retry.",
|
|
14307
14445
|
// Meta — high-frequency
|
|
14308
14446
|
META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
|
|
14309
14447
|
META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
|
|
@@ -14489,6 +14627,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
14489
14627
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
14490
14628
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
14491
14629
|
McpErrorCode.SHOPIFY_NOT_CONNECTED,
|
|
14630
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
14492
14631
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
14493
14632
|
McpErrorCode.NO_SHOPIFY_SHOPS,
|
|
14494
14633
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
@@ -14514,6 +14653,7 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
14514
14653
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
14515
14654
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
14516
14655
|
McpErrorCode.SHOPIFY_PUBLISH_FAILED,
|
|
14656
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
14517
14657
|
// Meta
|
|
14518
14658
|
McpErrorCode.META_NO_FB_PAGE,
|
|
14519
14659
|
McpErrorCode.META_NO_ADS_CREATED,
|
|
@@ -14941,9 +15081,14 @@ function renderAngleReview(pending, authorizedVideoCount) {
|
|
|
14941
15081
|
__name(renderAngleReview, "renderAngleReview");
|
|
14942
15082
|
function renderReferenceConfirmation(pending) {
|
|
14943
15083
|
if (pending.mode === "visual_recovery") {
|
|
14944
|
-
|
|
15084
|
+
const body = escapeMarkdownText(pending.reason ?? "Necesitamos revisar las fotos antes de continuar.");
|
|
15085
|
+
return pending.canKeepSelection ? `## Revisa las fotos del producto
|
|
14945
15086
|
|
|
14946
|
-
${
|
|
15087
|
+
${body}
|
|
15088
|
+
|
|
15089
|
+
Puedes **seguir con estas fotos** (sin costo adicional) llamando \`resolve_pending_action\` con \`kind: "product_reference_confirmation"\`, o abrir la tarea en ProductMaker Web para subir otras.` : `## Faltan fotos del producto
|
|
15090
|
+
|
|
15091
|
+
${body}
|
|
14947
15092
|
|
|
14948
15093
|
Abre esta tarea en ProductMaker Web para reemplazar o completar las fotos.`;
|
|
14949
15094
|
}
|
|
@@ -15205,7 +15350,7 @@ var CreateProductTaskInput = z54.object({
|
|
|
15205
15350
|
paymentMethods: z54.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.'),
|
|
15206
15351
|
productContext: z54.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."),
|
|
15207
15352
|
productLabel: z54.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.'),
|
|
15208
|
-
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" (
|
|
15353
|
+
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" (idea imposible: un truco visual que en la vida real no podr\xEDa pasar, y al cierre la invitaci\xF3n a comprar), "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.'),
|
|
15209
15354
|
durationSeconds: z54.union([
|
|
15210
15355
|
z54.literal(15),
|
|
15211
15356
|
z54.literal(30)
|
|
@@ -15432,6 +15577,8 @@ var StatusForResolveSchema = z54.object({
|
|
|
15432
15577
|
"question",
|
|
15433
15578
|
"visual_recovery"
|
|
15434
15579
|
]).optional(),
|
|
15580
|
+
/** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
|
|
15581
|
+
canKeepSelection: z54.boolean().optional(),
|
|
15435
15582
|
decisionKey: z54.string().optional(),
|
|
15436
15583
|
questionOrdinal: z54.number().int().min(1).max(5).optional(),
|
|
15437
15584
|
questionCeiling: z54.union([
|
|
@@ -15752,18 +15899,36 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
15752
15899
|
const snapshot = await pollStatus(fetcher, input.waitSeconds ?? 0, signal);
|
|
15753
15900
|
if (snapshot.status !== "done") {
|
|
15754
15901
|
const review = renderPendingActionMarkdown(snapshot);
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15902
|
+
if (review) {
|
|
15903
|
+
return {
|
|
15904
|
+
content: [
|
|
15905
|
+
{
|
|
15906
|
+
type: "text",
|
|
15907
|
+
text: review
|
|
15908
|
+
}
|
|
15909
|
+
],
|
|
15910
|
+
structuredContent: snapshot
|
|
15911
|
+
};
|
|
15912
|
+
}
|
|
15913
|
+
const continuation = buildPollingContinuation(snapshot);
|
|
15914
|
+
if (continuation) {
|
|
15915
|
+
return {
|
|
15916
|
+
content: [
|
|
15917
|
+
{
|
|
15918
|
+
type: "text",
|
|
15919
|
+
text: JSON.stringify({
|
|
15920
|
+
...snapshot,
|
|
15921
|
+
pollingContinuation: continuation
|
|
15922
|
+
})
|
|
15923
|
+
},
|
|
15924
|
+
{
|
|
15925
|
+
type: "text",
|
|
15926
|
+
text: continuation.instruction
|
|
15927
|
+
}
|
|
15928
|
+
]
|
|
15929
|
+
};
|
|
15930
|
+
}
|
|
15931
|
+
return asText(snapshot);
|
|
15767
15932
|
}
|
|
15768
15933
|
if (snapshot.kind === "service") {
|
|
15769
15934
|
const result = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-result`, {
|
|
@@ -15912,7 +16077,19 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
15912
16077
|
const candidateGrouping = pending.candidateGrouping;
|
|
15913
16078
|
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
15914
16079
|
if (pending.mode === "visual_recovery") {
|
|
15915
|
-
|
|
16080
|
+
if (!pending.canKeepSelection) {
|
|
16081
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
16082
|
+
}
|
|
16083
|
+
const kept = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
16084
|
+
selectedImageUrls: pending.selectedImageUrls ?? [],
|
|
16085
|
+
recovery: {
|
|
16086
|
+
action: "keep_selection",
|
|
16087
|
+
...pending.expectedEvidenceHash ? {
|
|
16088
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
16089
|
+
} : {}
|
|
16090
|
+
}
|
|
16091
|
+
});
|
|
16092
|
+
return asText(kept);
|
|
15916
16093
|
}
|
|
15917
16094
|
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
15918
16095
|
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
@@ -18630,6 +18807,16 @@ function renderMeta(meta) {
|
|
|
18630
18807
|
].filter(Boolean);
|
|
18631
18808
|
}
|
|
18632
18809
|
__name(renderMeta, "renderMeta");
|
|
18810
|
+
function renderDiscoveryLinks(links) {
|
|
18811
|
+
if (!links) return [];
|
|
18812
|
+
return [
|
|
18813
|
+
links.originalProductUrl ? `- [Producto original en TikTok Shop](${links.originalProductUrl})` : "- Producto original en TikTok Shop: no disponible",
|
|
18814
|
+
`- [Buscar d\xF3nde comprarlo](${links.googleSearchUrl})`,
|
|
18815
|
+
links.metaAdUrl ? `- [Anuncio relacionado encontrado en Meta](${links.metaAdUrl}) \u2014 comp\xE1ralo; puede ser una variante o un producto parecido` : "- Anuncio relacionado en Meta: no disponible",
|
|
18816
|
+
links.kaloDataUrl ? `- [Datos en KaloData](${links.kaloDataUrl})` : "- Datos en KaloData: no disponibles"
|
|
18817
|
+
];
|
|
18818
|
+
}
|
|
18819
|
+
__name(renderDiscoveryLinks, "renderDiscoveryLinks");
|
|
18633
18820
|
function renderCandidate(candidate, index) {
|
|
18634
18821
|
const name = candidate.nameLocal ?? candidate.nameOriginal;
|
|
18635
18822
|
const lines = [
|
|
@@ -18645,7 +18832,12 @@ function renderCandidate(candidate, index) {
|
|
|
18645
18832
|
]);
|
|
18646
18833
|
lines.push("", ...renderMeta(candidate.meta));
|
|
18647
18834
|
lines.push("", `- **Venta (30d):** ${num(candidate.units30d)} unidades \xB7 ${num(candidate.revenueUsd30d, " USD")} \xB7 crecimiento ${num(candidate.growthPct30d, "%")} \xB7 precio ${num(candidate.priceUsd, " USD")}`);
|
|
18648
|
-
|
|
18835
|
+
const discoveryLines = renderDiscoveryLinks(candidate.discoveryLinks);
|
|
18836
|
+
if (discoveryLines.length > 0) {
|
|
18837
|
+
lines.push("", "**D\xF3nde encontrarlo:**", ...discoveryLines);
|
|
18838
|
+
} else if (candidate.productUrl) {
|
|
18839
|
+
lines.push(`- [Ver producto](${candidate.productUrl})`);
|
|
18840
|
+
}
|
|
18649
18841
|
return lines.join("\n");
|
|
18650
18842
|
}
|
|
18651
18843
|
__name(renderCandidate, "renderCandidate");
|
package/dist/stdio.js
CHANGED
|
@@ -264,7 +264,7 @@ var ApiClient = class {
|
|
|
264
264
|
};
|
|
265
265
|
|
|
266
266
|
// src/index.ts
|
|
267
|
-
var MCP_VERSION = "6.
|
|
267
|
+
var MCP_VERSION = "6.3.1";
|
|
268
268
|
|
|
269
269
|
// src/meta-upload-app.ts
|
|
270
270
|
import { readFileSync } from "fs";
|
|
@@ -950,7 +950,25 @@ var AdaptiveProductReferenceStateSchema = z5.object({
|
|
|
950
950
|
* may enrich the candidate later without changing the photos the user
|
|
951
951
|
* approved; that must not invalidate the answer and reopen the question.
|
|
952
952
|
*/
|
|
953
|
-
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional()
|
|
953
|
+
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional(),
|
|
954
|
+
/**
|
|
955
|
+
* SHE PRESSED "SIGUE CON ESTAS FOTOS".
|
|
956
|
+
*
|
|
957
|
+
* The evidence version she pressed it at. The guard is
|
|
958
|
+
* `keptSelectionAtEvidenceVersion <= referenceEvidenceVersion`, so the
|
|
959
|
+
* retraction SURVIVES a later photo change — exactly like every other answer
|
|
960
|
+
* since answers stopped being filtered by evidence version. Recording the
|
|
961
|
+
* version is what makes a retraction taken at a FUTURE version (impossible
|
|
962
|
+
* today, but the ordering is asserted rather than assumed) not count.
|
|
963
|
+
*
|
|
964
|
+
* This is a RETRACTION, and it needs its own field because it cannot be
|
|
965
|
+
* expressed as an answer: the `different_products` answer that opened visual
|
|
966
|
+
* recovery short-circuits `buildAdaptiveProductReferenceDecision` before any
|
|
967
|
+
* later answer is read, so recording one more answer could never close the
|
|
968
|
+
* gate. Bumping the evidence version instead would drop every answer and
|
|
969
|
+
* restart the interview from `sale_structure` — the opposite of "continue".
|
|
970
|
+
*/
|
|
971
|
+
keptSelectionAtEvidenceVersion: z5.number().int().positive().optional()
|
|
954
972
|
});
|
|
955
973
|
var AdaptiveProductReferenceQuestionOptionSchema = z5.object({
|
|
956
974
|
value: ProductReferenceUserChoiceSchema,
|
|
@@ -3409,7 +3427,8 @@ var ShopifyPublishResponse = z22.object({
|
|
|
3409
3427
|
var ShopifyPublishErrorResponse = z22.object({
|
|
3410
3428
|
code: z22.enum([
|
|
3411
3429
|
"SHOPIFY_NOT_CONNECTED",
|
|
3412
|
-
"SHOPIFY_PUBLISH_FAILED"
|
|
3430
|
+
"SHOPIFY_PUBLISH_FAILED",
|
|
3431
|
+
"SHOPIFY_NO_PUBLISHED_THEME"
|
|
3413
3432
|
]),
|
|
3414
3433
|
message: z22.string().optional(),
|
|
3415
3434
|
authUrl: z22.string().url().optional(),
|
|
@@ -3551,9 +3570,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3551
3570
|
referenceEvidenceVersion: z23.number().int().positive().optional(),
|
|
3552
3571
|
expectedEvidenceHash: z23.string().min(1).max(128).optional(),
|
|
3553
3572
|
recovery: z23.object({
|
|
3573
|
+
/**
|
|
3574
|
+
* `keep_selection` is NOT a recovery in the billing sense: it commits
|
|
3575
|
+
* no new evidence, re-enqueues no extract, and costs nothing. It rides
|
|
3576
|
+
* on this field because it answers the same card, and the server
|
|
3577
|
+
* branches on it BEFORE the billed recovery path.
|
|
3578
|
+
*/
|
|
3554
3579
|
action: z23.enum([
|
|
3555
3580
|
"replace_selection",
|
|
3556
|
-
"refine_selection"
|
|
3581
|
+
"refine_selection",
|
|
3582
|
+
"keep_selection"
|
|
3557
3583
|
]),
|
|
3558
3584
|
affectedRefKeys: z23.array(z23.string().min(1).max(200)).min(1).max(5).optional(),
|
|
3559
3585
|
expectedReferenceVersionId: z23.string().uuid().optional(),
|
|
@@ -3561,6 +3587,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3561
3587
|
uploadedImages: z23.array(ProductReferenceRecoveryUpload).max(INGEST_MAX_REFERENCE_IMAGES).optional()
|
|
3562
3588
|
}).optional()
|
|
3563
3589
|
}).superRefine((value, context) => {
|
|
3590
|
+
if (value.recovery?.action === "keep_selection" && value.recovery.uploadedImages?.length) {
|
|
3591
|
+
context.addIssue({
|
|
3592
|
+
code: z23.ZodIssueCode.custom,
|
|
3593
|
+
path: [
|
|
3594
|
+
"recovery",
|
|
3595
|
+
"uploadedImages"
|
|
3596
|
+
],
|
|
3597
|
+
message: "keep_selection_does_not_accept_uploads"
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3564
3600
|
if (value.recovery?.action === "refine_selection" && value.recovery.uploadedImages?.length) {
|
|
3565
3601
|
context.addIssue({
|
|
3566
3602
|
code: z23.ZodIssueCode.custom,
|
|
@@ -4128,6 +4164,16 @@ var PendingUserActionSchema = z26.discriminatedUnion("kind", [
|
|
|
4128
4164
|
referenceEvidenceVersion: z26.number().int().positive().optional(),
|
|
4129
4165
|
expectedEvidenceHash: z26.string().min(1).max(128).optional(),
|
|
4130
4166
|
question: AdaptiveProductReferenceQuestionSchema.optional(),
|
|
4167
|
+
/**
|
|
4168
|
+
* TRUE when "sigue con estas fotos" is a real exit from this card.
|
|
4169
|
+
*
|
|
4170
|
+
* Absent means the seller genuinely has to supply pixels we do not have,
|
|
4171
|
+
* and offering to continue would be a lie: nothing is selected at all, or
|
|
4172
|
+
* a combo component was never photographed. Every client reads this — web,
|
|
4173
|
+
* MCP and WhatsApp — so no surface can invent an exit the server did not
|
|
4174
|
+
* offer, nor omit one it did.
|
|
4175
|
+
*/
|
|
4176
|
+
canKeepSelection: z26.boolean().optional(),
|
|
4131
4177
|
recovery: z26.object({
|
|
4132
4178
|
kind: z26.literal("combo_component_evidence"),
|
|
4133
4179
|
minimumImageCount: z26.number().int().min(2).max(5),
|
|
@@ -4304,6 +4350,28 @@ var StatusResponse = z26.object({
|
|
|
4304
4350
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
4305
4351
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
4306
4352
|
hasAnswerableGate: z26.boolean().optional(),
|
|
4353
|
+
/**
|
|
4354
|
+
* "Is the ANGLE gate — and only that gate — still open?"
|
|
4355
|
+
*
|
|
4356
|
+
* `needsReview` is an OR over every gate (reference, angles, SCRIPT review,
|
|
4357
|
+
* grouping). The chat used to read that OR as proof that the server was
|
|
4358
|
+
* waiting on ANGLES, because the two conditions it paired it with
|
|
4359
|
+
* (`stageStatuses.video_generator === 'pending'` + `status === 'processing'`)
|
|
4360
|
+
* are ALSO true throughout script review. So the moment a script became
|
|
4361
|
+
* ready — a gate the seller had already passed the angle card to reach — the
|
|
4362
|
+
* chat "recovered" the angle review card over a run that was already
|
|
4363
|
+
* dispatched, released its own submit guard, and re-enabled «Generar». The
|
|
4364
|
+
* seller pressed it again; the second resume lost the compare-and-clear and
|
|
4365
|
+
* answered 201 with a plain status body, so the no-op read as success and the
|
|
4366
|
+
* loop repeated (measured 2026-08-27: three resumes in 16s, two of them
|
|
4367
|
+
* no-ops).
|
|
4368
|
+
*
|
|
4369
|
+
* Named rather than inferred, for the same reason as `hasAnswerableGate`: a
|
|
4370
|
+
* gate added tomorrow widens `needsReview` and would silently re-arm that
|
|
4371
|
+
* recovery path. Absent (undefined) means "an older API that cannot answer" —
|
|
4372
|
+
* clients must fall back to `pauseForAngleReview` alone, never to the OR.
|
|
4373
|
+
*/
|
|
4374
|
+
awaitingAngleReview: z26.boolean().optional(),
|
|
4307
4375
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
4308
4376
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
4309
4377
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -10847,6 +10915,10 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10847
10915
|
defineCapability("cost_product", "6d1f85a999dbdabae4eda406ff4689cdff2016df375b5b6741b91f4308d819af", "none", "read_only", [
|
|
10848
10916
|
"read"
|
|
10849
10917
|
], [
|
|
10918
|
+
slot("product_reference", false, [], [
|
|
10919
|
+
"selectedProductTitle",
|
|
10920
|
+
"selectedProductUrl"
|
|
10921
|
+
], "server"),
|
|
10850
10922
|
slot("market", true, [
|
|
10851
10923
|
"country"
|
|
10852
10924
|
]),
|
|
@@ -10917,7 +10989,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10917
10989
|
], [
|
|
10918
10990
|
"creative_variant"
|
|
10919
10991
|
]),
|
|
10920
|
-
defineCapability("create_product_task", "
|
|
10992
|
+
defineCapability("create_product_task", "13a037c7365cc4c30d81a1d89515e663a3de92ffd4138a117fa32ecb629bca44", "credit_consuming", "creative", [
|
|
10921
10993
|
"credit_consuming",
|
|
10922
10994
|
"state_change"
|
|
10923
10995
|
], [
|
|
@@ -11148,7 +11220,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11148
11220
|
"images"
|
|
11149
11221
|
], [
|
|
11150
11222
|
"imageMessageIds",
|
|
11151
|
-
"projectId"
|
|
11223
|
+
"projectId",
|
|
11224
|
+
"productUrl"
|
|
11152
11225
|
]),
|
|
11153
11226
|
slot("product_identity", true, [
|
|
11154
11227
|
"productTitle"
|
|
@@ -11220,7 +11293,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11220
11293
|
"images"
|
|
11221
11294
|
], [
|
|
11222
11295
|
"imageMessageIds",
|
|
11223
|
-
"projectId"
|
|
11296
|
+
"projectId",
|
|
11297
|
+
"productUrl"
|
|
11224
11298
|
]),
|
|
11225
11299
|
slot("locale", true, [
|
|
11226
11300
|
"language",
|
|
@@ -11287,6 +11361,15 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11287
11361
|
explicitOmissionAllowed: false,
|
|
11288
11362
|
fieldCompletion: "all"
|
|
11289
11363
|
},
|
|
11364
|
+
{
|
|
11365
|
+
slot: "sales_context",
|
|
11366
|
+
askBeforePaidExecution: true,
|
|
11367
|
+
explicitOmissionAllowed: false,
|
|
11368
|
+
fieldCompletion: "any",
|
|
11369
|
+
completionFields: [
|
|
11370
|
+
"salesAngle"
|
|
11371
|
+
]
|
|
11372
|
+
},
|
|
11290
11373
|
{
|
|
11291
11374
|
slot: "commercial_offer",
|
|
11292
11375
|
askBeforePaidExecution: true,
|
|
@@ -11302,7 +11385,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11302
11385
|
"images"
|
|
11303
11386
|
], [
|
|
11304
11387
|
"imageMessageIds",
|
|
11305
|
-
"projectId"
|
|
11388
|
+
"projectId",
|
|
11389
|
+
"productUrl"
|
|
11306
11390
|
]),
|
|
11307
11391
|
slot("product_identity", true, [
|
|
11308
11392
|
"title"
|
|
@@ -11355,6 +11439,25 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11355
11439
|
"product_reference"
|
|
11356
11440
|
], [
|
|
11357
11441
|
"video"
|
|
11442
|
+
], SHARED_CHANNEL_EXPOSURE, [
|
|
11443
|
+
{
|
|
11444
|
+
slot: "product_reference",
|
|
11445
|
+
askBeforePaidExecution: true,
|
|
11446
|
+
explicitOmissionAllowed: false,
|
|
11447
|
+
fieldCompletion: "any"
|
|
11448
|
+
},
|
|
11449
|
+
{
|
|
11450
|
+
slot: "locale",
|
|
11451
|
+
askBeforePaidExecution: true,
|
|
11452
|
+
explicitOmissionAllowed: false,
|
|
11453
|
+
fieldCompletion: "all"
|
|
11454
|
+
},
|
|
11455
|
+
{
|
|
11456
|
+
slot: "commercial_offer",
|
|
11457
|
+
askBeforePaidExecution: true,
|
|
11458
|
+
explicitOmissionAllowed: true,
|
|
11459
|
+
fieldCompletion: "all"
|
|
11460
|
+
}
|
|
11358
11461
|
]),
|
|
11359
11462
|
defineCapability("get_meta_campaign_status", "92077241cf4e9d14edefca410e85715cc383b1433081c3461c925f8fc3d6e8ab", "none", "read_only", [
|
|
11360
11463
|
"read"
|
|
@@ -11983,6 +12086,10 @@ var ReferralDashboardOverviewSchema = z51.object({
|
|
|
11983
12086
|
monthlyRegistrations: z51.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
|
|
11984
12087
|
});
|
|
11985
12088
|
|
|
12089
|
+
// ../../packages/types/src/whatsapp.ts
|
|
12090
|
+
var WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
|
|
12091
|
+
var WHATSAPP_MAX_AUDIO_DURATION_SECONDS = 10 * 60;
|
|
12092
|
+
|
|
11986
12093
|
// src/tools/tasks.ts
|
|
11987
12094
|
import { z as z53 } from "zod";
|
|
11988
12095
|
|
|
@@ -12265,6 +12372,35 @@ function sleep(ms, signal) {
|
|
|
12265
12372
|
}
|
|
12266
12373
|
__name(sleep, "sleep");
|
|
12267
12374
|
|
|
12375
|
+
// src/polling-continuation.ts
|
|
12376
|
+
var NO_CONTINUATION_STATUSES = /* @__PURE__ */ new Set([
|
|
12377
|
+
"done",
|
|
12378
|
+
"complete",
|
|
12379
|
+
"error",
|
|
12380
|
+
"awaiting_review"
|
|
12381
|
+
]);
|
|
12382
|
+
var SUGGESTED_WAIT_SECONDS = 45;
|
|
12383
|
+
function buildPollingContinuation(snapshot, opts = {}) {
|
|
12384
|
+
if (NO_CONTINUATION_STATUSES.has(snapshot.status)) return null;
|
|
12385
|
+
if (snapshot.pendingUserAction != null) return null;
|
|
12386
|
+
const suggestedWaitSeconds = opts.suggestedWaitSeconds ?? SUGGESTED_WAIT_SECONDS;
|
|
12387
|
+
return {
|
|
12388
|
+
stillRunning: true,
|
|
12389
|
+
nextAction: "continue_polling_get_task_status",
|
|
12390
|
+
suggestedWaitSeconds,
|
|
12391
|
+
instruction: `La tarea SIGUE CORRIENDO: no termin\xF3, no fall\xF3 y todav\xEDa no hay nada que el usuario deba revisar. Esto NO es un error ni un final de trabajo.
|
|
12392
|
+
|
|
12393
|
+
REGLA: no termines tu turno sin dejar ARMADA la siguiente consulta. Cerrar el turno est\xE1 bien; cerrarlo sin nada que vuelva a preguntar es lo que deja al usuario esperando para siempre una revisi\xF3n que nunca le vas a mostrar.
|
|
12394
|
+
|
|
12395
|
+
Elige seg\xFAn lo que tu host permita:
|
|
12396
|
+
1. PREFERIDO \u2014 si puedes agendar un mensaje o turno futuro (mensajes programados, recordatorios): agenda uno a ~2 minutos que vuelva a llamar get_task_status con este mismo taskId, dile al usuario que le avisas, y ah\xED s\xED cierra el turno. Es la mejor opci\xF3n: el usuario queda libre y t\xFA vuelves solo.
|
|
12397
|
+
2. SI NO PUEDES AGENDAR: llama otra vez a get_task_status AHORA MISMO con waitSeconds=${suggestedWaitSeconds}, y repite hasta un estado terminal (done/error) o una acci\xF3n pendiente del usuario. NO cierres el turno con un "te aviso cuando est\xE9": nadie te va a volver a despertar.
|
|
12398
|
+
|
|
12399
|
+
No subas waitSeconds por encima de ${suggestedWaitSeconds} salvo que sepas que tu host aguanta llamadas m\xE1s largas: muchos clientes MCP cancelan a los 60 s y ver\xEDas un error de timeout en vez del estado.`
|
|
12400
|
+
};
|
|
12401
|
+
}
|
|
12402
|
+
__name(buildPollingContinuation, "buildPollingContinuation");
|
|
12403
|
+
|
|
12268
12404
|
// src/errors.ts
|
|
12269
12405
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
12270
12406
|
|
|
@@ -12982,6 +13118,7 @@ var McpErrorCode = {
|
|
|
12982
13118
|
SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
|
|
12983
13119
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
|
|
12984
13120
|
SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
|
|
13121
|
+
SHOPIFY_NO_PUBLISHED_THEME: "SHOPIFY_NO_PUBLISHED_THEME",
|
|
12985
13122
|
// Meta / Facebook Ads
|
|
12986
13123
|
META_NO_FB_PAGE: "META_NO_FB_PAGE",
|
|
12987
13124
|
META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
|
|
@@ -13049,6 +13186,7 @@ var CODE_MESSAGES = {
|
|
|
13049
13186
|
SHOPIFY_TOKEN_EXPIRED: "The Shopify session expired. Reconnect the store at https://productmaker.app/integrations.",
|
|
13050
13187
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "The Shopify connection needs additional scopes. Reconnect it at https://productmaker.app/integrations to approve the new permissions.",
|
|
13051
13188
|
SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
|
|
13189
|
+
SHOPIFY_NO_PUBLISHED_THEME: "The Shopify store has no published theme, so there is nowhere to place the landing. Ask the user to publish a theme (Online Store > Themes > Publish) and retry.",
|
|
13052
13190
|
// Meta — high-frequency
|
|
13053
13191
|
META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
|
|
13054
13192
|
META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
|
|
@@ -13234,6 +13372,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
13234
13372
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
13235
13373
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
13236
13374
|
McpErrorCode.SHOPIFY_NOT_CONNECTED,
|
|
13375
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
13237
13376
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
13238
13377
|
McpErrorCode.NO_SHOPIFY_SHOPS,
|
|
13239
13378
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
@@ -13259,6 +13398,7 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
13259
13398
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
13260
13399
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
13261
13400
|
McpErrorCode.SHOPIFY_PUBLISH_FAILED,
|
|
13401
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
13262
13402
|
// Meta
|
|
13263
13403
|
McpErrorCode.META_NO_FB_PAGE,
|
|
13264
13404
|
McpErrorCode.META_NO_ADS_CREATED,
|
|
@@ -13739,9 +13879,14 @@ function renderAngleReview(pending, authorizedVideoCount) {
|
|
|
13739
13879
|
__name(renderAngleReview, "renderAngleReview");
|
|
13740
13880
|
function renderReferenceConfirmation(pending) {
|
|
13741
13881
|
if (pending.mode === "visual_recovery") {
|
|
13742
|
-
|
|
13882
|
+
const body = escapeMarkdownText(pending.reason ?? "Necesitamos revisar las fotos antes de continuar.");
|
|
13883
|
+
return pending.canKeepSelection ? `## Revisa las fotos del producto
|
|
13743
13884
|
|
|
13744
|
-
${
|
|
13885
|
+
${body}
|
|
13886
|
+
|
|
13887
|
+
Puedes **seguir con estas fotos** (sin costo adicional) llamando \`resolve_pending_action\` con \`kind: "product_reference_confirmation"\`, o abrir la tarea en ProductMaker Web para subir otras.` : `## Faltan fotos del producto
|
|
13888
|
+
|
|
13889
|
+
${body}
|
|
13745
13890
|
|
|
13746
13891
|
Abre esta tarea en ProductMaker Web para reemplazar o completar las fotos.`;
|
|
13747
13892
|
}
|
|
@@ -14003,7 +14148,7 @@ var CreateProductTaskInput = z53.object({
|
|
|
14003
14148
|
paymentMethods: z53.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.'),
|
|
14004
14149
|
productContext: z53.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."),
|
|
14005
14150
|
productLabel: z53.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.'),
|
|
14006
|
-
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" (
|
|
14151
|
+
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" (idea imposible: un truco visual que en la vida real no podr\xEDa pasar, y al cierre la invitaci\xF3n a comprar), "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.'),
|
|
14007
14152
|
durationSeconds: z53.union([
|
|
14008
14153
|
z53.literal(15),
|
|
14009
14154
|
z53.literal(30)
|
|
@@ -14230,6 +14375,8 @@ var StatusForResolveSchema = z53.object({
|
|
|
14230
14375
|
"question",
|
|
14231
14376
|
"visual_recovery"
|
|
14232
14377
|
]).optional(),
|
|
14378
|
+
/** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
|
|
14379
|
+
canKeepSelection: z53.boolean().optional(),
|
|
14233
14380
|
decisionKey: z53.string().optional(),
|
|
14234
14381
|
questionOrdinal: z53.number().int().min(1).max(5).optional(),
|
|
14235
14382
|
questionCeiling: z53.union([
|
|
@@ -14550,18 +14697,36 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
14550
14697
|
const snapshot = await pollStatus(fetcher, input.waitSeconds ?? 0, signal);
|
|
14551
14698
|
if (snapshot.status !== "done") {
|
|
14552
14699
|
const review = renderPendingActionMarkdown(snapshot);
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14700
|
+
if (review) {
|
|
14701
|
+
return {
|
|
14702
|
+
content: [
|
|
14703
|
+
{
|
|
14704
|
+
type: "text",
|
|
14705
|
+
text: review
|
|
14706
|
+
}
|
|
14707
|
+
],
|
|
14708
|
+
structuredContent: snapshot
|
|
14709
|
+
};
|
|
14710
|
+
}
|
|
14711
|
+
const continuation = buildPollingContinuation(snapshot);
|
|
14712
|
+
if (continuation) {
|
|
14713
|
+
return {
|
|
14714
|
+
content: [
|
|
14715
|
+
{
|
|
14716
|
+
type: "text",
|
|
14717
|
+
text: JSON.stringify({
|
|
14718
|
+
...snapshot,
|
|
14719
|
+
pollingContinuation: continuation
|
|
14720
|
+
})
|
|
14721
|
+
},
|
|
14722
|
+
{
|
|
14723
|
+
type: "text",
|
|
14724
|
+
text: continuation.instruction
|
|
14725
|
+
}
|
|
14726
|
+
]
|
|
14727
|
+
};
|
|
14728
|
+
}
|
|
14729
|
+
return asText(snapshot);
|
|
14565
14730
|
}
|
|
14566
14731
|
if (snapshot.kind === "service") {
|
|
14567
14732
|
const result = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-result`, {
|
|
@@ -14710,7 +14875,19 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
14710
14875
|
const candidateGrouping = pending.candidateGrouping;
|
|
14711
14876
|
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
14712
14877
|
if (pending.mode === "visual_recovery") {
|
|
14713
|
-
|
|
14878
|
+
if (!pending.canKeepSelection) {
|
|
14879
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
14880
|
+
}
|
|
14881
|
+
const kept = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
14882
|
+
selectedImageUrls: pending.selectedImageUrls ?? [],
|
|
14883
|
+
recovery: {
|
|
14884
|
+
action: "keep_selection",
|
|
14885
|
+
...pending.expectedEvidenceHash ? {
|
|
14886
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
14887
|
+
} : {}
|
|
14888
|
+
}
|
|
14889
|
+
});
|
|
14890
|
+
return asText(kept);
|
|
14714
14891
|
}
|
|
14715
14892
|
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
14716
14893
|
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
@@ -17428,6 +17605,16 @@ function renderMeta(meta) {
|
|
|
17428
17605
|
].filter(Boolean);
|
|
17429
17606
|
}
|
|
17430
17607
|
__name(renderMeta, "renderMeta");
|
|
17608
|
+
function renderDiscoveryLinks(links) {
|
|
17609
|
+
if (!links) return [];
|
|
17610
|
+
return [
|
|
17611
|
+
links.originalProductUrl ? `- [Producto original en TikTok Shop](${links.originalProductUrl})` : "- Producto original en TikTok Shop: no disponible",
|
|
17612
|
+
`- [Buscar d\xF3nde comprarlo](${links.googleSearchUrl})`,
|
|
17613
|
+
links.metaAdUrl ? `- [Anuncio relacionado encontrado en Meta](${links.metaAdUrl}) \u2014 comp\xE1ralo; puede ser una variante o un producto parecido` : "- Anuncio relacionado en Meta: no disponible",
|
|
17614
|
+
links.kaloDataUrl ? `- [Datos en KaloData](${links.kaloDataUrl})` : "- Datos en KaloData: no disponibles"
|
|
17615
|
+
];
|
|
17616
|
+
}
|
|
17617
|
+
__name(renderDiscoveryLinks, "renderDiscoveryLinks");
|
|
17431
17618
|
function renderCandidate(candidate, index) {
|
|
17432
17619
|
const name = candidate.nameLocal ?? candidate.nameOriginal;
|
|
17433
17620
|
const lines = [
|
|
@@ -17443,7 +17630,12 @@ function renderCandidate(candidate, index) {
|
|
|
17443
17630
|
]);
|
|
17444
17631
|
lines.push("", ...renderMeta(candidate.meta));
|
|
17445
17632
|
lines.push("", `- **Venta (30d):** ${num(candidate.units30d)} unidades \xB7 ${num(candidate.revenueUsd30d, " USD")} \xB7 crecimiento ${num(candidate.growthPct30d, "%")} \xB7 precio ${num(candidate.priceUsd, " USD")}`);
|
|
17446
|
-
|
|
17633
|
+
const discoveryLines = renderDiscoveryLinks(candidate.discoveryLinks);
|
|
17634
|
+
if (discoveryLines.length > 0) {
|
|
17635
|
+
lines.push("", "**D\xF3nde encontrarlo:**", ...discoveryLines);
|
|
17636
|
+
} else if (candidate.productUrl) {
|
|
17637
|
+
lines.push(`- [Ver producto](${candidate.productUrl})`);
|
|
17638
|
+
}
|
|
17447
17639
|
return lines.join("\n");
|
|
17448
17640
|
}
|
|
17449
17641
|
__name(renderCandidate, "renderCandidate");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",
|