docs-combiner 0.4.0 → 1.0.0
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/agentApi/agentControlClaim.js +26 -0
- package/dist/agentApi/approachMatrixView.js +19 -0
- package/dist/agentApi/capabilitiesMeta.js +78 -0
- package/dist/agentApi/catalogUrlPresets.js +26 -0
- package/dist/agentApi/constants.js +11 -0
- package/dist/agentApi/creativeSelections.js +25 -0
- package/dist/agentApi/escalation.js +221 -0
- package/dist/agentApi/generatedPairsGuard.js +85 -0
- package/dist/agentApi/httpHelpers.js +77 -0
- package/dist/agentApi/imagesReadiness.js +60 -0
- package/dist/agentApi/jobStatus.js +113 -0
- package/dist/agentApi/productImageDrive.js +39 -0
- package/dist/agentApi/regenerateImages.js +125 -0
- package/dist/agentApi/rendererTypes.js +3 -0
- package/dist/agentApi/routes.js +440 -0
- package/dist/agentApi/sessionSnapshot.js +82 -0
- package/dist/agentApi/sessionTypes.js +2 -0
- package/dist/agentApi/spec.js +1045 -0
- package/dist/agentApi/types.js +2 -0
- package/dist/agentApi/validation.js +32 -0
- package/dist/campaignsCsv.js +420 -0
- package/dist/contentPairs.js +62 -0
- package/dist/flexcardBalance.js +88 -0
- package/dist/integrations/driveApi.js +420 -0
- package/dist/integrations/httpTimeout.js +116 -0
- package/dist/integrations/openrouter.js +532 -0
- package/dist/main.js +316 -1214
- package/dist/models.js +17 -0
- package/dist/offerSettings.js +19 -0
- package/dist/preload.js +9 -0
- package/dist/promptOverrides.js +437 -0
- package/dist/prompts.js +1243 -0
- package/dist/renderer.js +19 -19
- package/dist/renderer.js.map +1 -1
- package/dist/server/app.js +378 -0
- package/dist/server/auth.js +74 -0
- package/dist/server/contentJobs.js +367 -0
- package/dist/server/driveCatalog.js +122 -0
- package/dist/server/driveProxy.js +220 -0
- package/dist/server/flexcardMeta.js +29 -0
- package/dist/server/googleAuth.js +84 -0
- package/dist/server/imageAssets.js +87 -0
- package/dist/server/imageJobs.js +776 -0
- package/dist/server/index.js +38 -0
- package/dist/server/jobEvents.js +116 -0
- package/dist/server/jobs.js +358 -0
- package/dist/server/openRouterMeta.js +58 -0
- package/dist/server/spec.js +544 -0
- package/dist/server/storage.js +133 -0
- package/dist/server/telegram.js +53 -0
- package/dist/server/workspaceSnapshot.js +273 -0
- package/package.json +15 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldClaimAgentControl = shouldClaimAgentControl;
|
|
4
|
+
const AGENT_CONTROL_CLAIMING_METHODS = new Set([
|
|
5
|
+
'setupCampaignWorkspace',
|
|
6
|
+
'createCampaignGroup',
|
|
7
|
+
'createWorkspaceFolder',
|
|
8
|
+
'startGeneration',
|
|
9
|
+
'patchSessionInfo',
|
|
10
|
+
'fillGeoBlock',
|
|
11
|
+
'setCreativeSelections',
|
|
12
|
+
'setApproachMatrix',
|
|
13
|
+
'patchGenerated',
|
|
14
|
+
'loadContent',
|
|
15
|
+
'uploadImages',
|
|
16
|
+
'checkImages',
|
|
17
|
+
'regeneratePair',
|
|
18
|
+
'regenerateImages',
|
|
19
|
+
]);
|
|
20
|
+
function shouldClaimAgentControl(method, options) {
|
|
21
|
+
if ((options === null || options === void 0 ? void 0 : options.claimAgentControl) === false)
|
|
22
|
+
return false;
|
|
23
|
+
if ((options === null || options === void 0 ? void 0 : options.claimAgentControl) === true)
|
|
24
|
+
return true;
|
|
25
|
+
return AGENT_CONTROL_CLAIMING_METHODS.has(method);
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildApproachMatrixView = buildApproachMatrixView;
|
|
4
|
+
const promptOverrides_1 = require("../promptOverrides");
|
|
5
|
+
function buildApproachMatrixView(enabled, rows) {
|
|
6
|
+
const textIndices = (0, promptOverrides_1.textApproachIndicesFromMatrix)(rows);
|
|
7
|
+
return {
|
|
8
|
+
ok: true,
|
|
9
|
+
enabled,
|
|
10
|
+
rows,
|
|
11
|
+
derived: {
|
|
12
|
+
selectedTextApproachIndices: textIndices,
|
|
13
|
+
selectedTextApproachNumbers: textIndices.map(index => index + 1),
|
|
14
|
+
imageApproachCounts: (0, promptOverrides_1.imageApproachCountsFromMatrix)(rows),
|
|
15
|
+
},
|
|
16
|
+
format: 'Array<[imageApproachNumber, textApproachNumbers[]]>',
|
|
17
|
+
example: [[4, [3, 8, 10, 1]], [6, [3, 2, 4]]],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAgentCapabilitiesMeta = buildAgentCapabilitiesMeta;
|
|
4
|
+
const catalogUrlPresets_1 = require("./catalogUrlPresets");
|
|
5
|
+
function buildAgentCapabilitiesMeta() {
|
|
6
|
+
return {
|
|
7
|
+
actions: {
|
|
8
|
+
listSessions: true,
|
|
9
|
+
createSession: true,
|
|
10
|
+
readDriveCatalog: true,
|
|
11
|
+
setupCampaignWorkspace: true,
|
|
12
|
+
readSession: true,
|
|
13
|
+
patchSession: true,
|
|
14
|
+
readValidation: true,
|
|
15
|
+
readGenerated: true,
|
|
16
|
+
patchGenerated: true,
|
|
17
|
+
readLogs: true,
|
|
18
|
+
readJobStatus: true,
|
|
19
|
+
readApproachMatrix: true,
|
|
20
|
+
setApproachMatrix: true,
|
|
21
|
+
loadContent: true,
|
|
22
|
+
uploadImages: true,
|
|
23
|
+
checkImages: true,
|
|
24
|
+
regeneratePair: true,
|
|
25
|
+
regenerateImages: true,
|
|
26
|
+
setAgentControlMode: true,
|
|
27
|
+
readAgentControlStatus: true,
|
|
28
|
+
startGeneration: true,
|
|
29
|
+
exportCatalog: true,
|
|
30
|
+
createCampaignGroup: true,
|
|
31
|
+
createWorkspaceFolder: true,
|
|
32
|
+
},
|
|
33
|
+
fields: {
|
|
34
|
+
sessionPatch: [
|
|
35
|
+
'drive.groupName',
|
|
36
|
+
'drive.workspaceFolderId',
|
|
37
|
+
'drive.workspaceName',
|
|
38
|
+
'drive.offer',
|
|
39
|
+
'generation.product',
|
|
40
|
+
'generation.additionalInfo',
|
|
41
|
+
'generation.firstGeo',
|
|
42
|
+
'generation.firstPriceWithCurrency',
|
|
43
|
+
'generation.firstLink',
|
|
44
|
+
'generation.geoBlock',
|
|
45
|
+
'catalogUrlSettings',
|
|
46
|
+
'creativeSelections',
|
|
47
|
+
'creativeSelections.matrix',
|
|
48
|
+
'promptSettings.approachMatrix',
|
|
49
|
+
],
|
|
50
|
+
setupCampaign: ['groupName', 'workspaceFolderId', 'workspaceName', 'offer', 'generation', 'creativeSelections', 'catalogUrlSettings'],
|
|
51
|
+
driveCatalog: ['offers', 'campaignGroups', 'workspaces', 'campaignsCsv'],
|
|
52
|
+
createCampaignGroup: ['name'],
|
|
53
|
+
createWorkspaceFolder: ['groupName', 'name', 'select'],
|
|
54
|
+
catalogUrlSettings: {
|
|
55
|
+
includeTextApproach: 'boolean',
|
|
56
|
+
includeCreoApproach: 'boolean',
|
|
57
|
+
includeImageAspect: 'boolean',
|
|
58
|
+
textApproachParam: 'string (default sub19)',
|
|
59
|
+
creoApproachParam: 'string (default sub20)',
|
|
60
|
+
imageAspectParam: 'string (default sub18)',
|
|
61
|
+
extraMacrosPreset: '"redtrack" | "keitaro"',
|
|
62
|
+
extraMacros: 'string — custom tail; overrides extraMacrosPreset if both sent',
|
|
63
|
+
presets: catalogUrlPresets_1.CATALOG_LINK_EXTRA_MACROS_PRESETS,
|
|
64
|
+
},
|
|
65
|
+
generation: ['content', 'images', 'catalog', 'all'],
|
|
66
|
+
generationAsync: 'POST .../generate with { "async": true } returns 202; poll GET .../jobs/{jobId}?wait=1',
|
|
67
|
+
sessionActions: ['load-content', 'upload-images', 'check-images', 'regenerate-pair', 'regenerate-images'],
|
|
68
|
+
generatedPatch: ['titles', 'texts', 'images'],
|
|
69
|
+
approachMatrix: {
|
|
70
|
+
read: 'getApproachMatrix()',
|
|
71
|
+
write: 'setApproachMatrix({ enabled: true, rows: [[4, [3, 8, 10, 1]], [6, [3, 2, 4]]] })',
|
|
72
|
+
viaCreativeSelections: 'setCreativeSelections({ matrix: { enabled: true, rows: [[4, [3, 8, 10, 1]]] } })',
|
|
73
|
+
},
|
|
74
|
+
escalation: 'On { ok: false } and validation.blocking — optional escalation block with userMessage for the human operator',
|
|
75
|
+
},
|
|
76
|
+
jobs: ['content', 'images', 'regenerateImages', 'replaceCatalog', 'loadContent', 'uploadImages', 'checkImages'],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CATALOG_LINK_EXTRA_MACROS_PRESETS = exports.DEFAULT_CATALOG_LINK_KEITARO_MACROS = exports.DEFAULT_CATALOG_LINK_EXTRA_MACROS = void 0;
|
|
4
|
+
exports.resolveCatalogLinkExtraMacros = resolveCatalogLinkExtraMacros;
|
|
5
|
+
exports.DEFAULT_CATALOG_LINK_EXTRA_MACROS = 'sub1={{ad.id}}&sub2={{adset.id}}&sub3={{campaign.id}}&sub4={{ad.name}}&sub5={{adset.name}}&sub6={{campaign.name}}&sub7={{placement}}&sub8={{site_source_name}}&utm_source=facebook&utm_medium=paid';
|
|
6
|
+
exports.DEFAULT_CATALOG_LINK_KEITARO_MACROS = 'utm_campaign={{campaign.name}}&utm_source={{site_source_name}}&utm_placement={{placement}}&campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}&adset_name={{adset.name}}&ad_name={{ad.name}}';
|
|
7
|
+
exports.CATALOG_LINK_EXTRA_MACROS_PRESETS = {
|
|
8
|
+
redtrack: exports.DEFAULT_CATALOG_LINK_EXTRA_MACROS,
|
|
9
|
+
keitaro: exports.DEFAULT_CATALOG_LINK_KEITARO_MACROS,
|
|
10
|
+
};
|
|
11
|
+
function resolveCatalogLinkExtraMacros(presetRaw, extraMacrosRaw, hasPreset, hasExtraMacros) {
|
|
12
|
+
if (hasExtraMacros) {
|
|
13
|
+
return { ok: true, value: String(extraMacrosRaw !== null && extraMacrosRaw !== void 0 ? extraMacrosRaw : '') };
|
|
14
|
+
}
|
|
15
|
+
if (hasPreset) {
|
|
16
|
+
const preset = String(presetRaw !== null && presetRaw !== void 0 ? presetRaw : '').trim().toLowerCase();
|
|
17
|
+
if (!(preset in exports.CATALOG_LINK_EXTRA_MACROS_PRESETS)) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: `Unknown extraMacrosPreset: ${String(presetRaw)}. Use "redtrack" or "keitaro".`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, value: exports.CATALOG_LINK_EXTRA_MACROS_PRESETS[preset] };
|
|
24
|
+
}
|
|
25
|
+
return { ok: true, value: '' };
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_AGENT_API_PORT = exports.DEFAULT_AGENT_API_HOST = void 0;
|
|
4
|
+
exports.getDefaultAgentApiBaseUrl = getDefaultAgentApiBaseUrl;
|
|
5
|
+
exports.DEFAULT_AGENT_API_HOST = '127.0.0.1';
|
|
6
|
+
exports.DEFAULT_AGENT_API_PORT = 17321;
|
|
7
|
+
function getDefaultAgentApiBaseUrl() {
|
|
8
|
+
const rawPort = Number(process.env.DOCS_COMBINER_AGENT_API_PORT);
|
|
9
|
+
const port = Number.isInteger(rawPort) && rawPort > 0 && rawPort <= 65535 ? rawPort : exports.DEFAULT_AGENT_API_PORT;
|
|
10
|
+
return `http://${exports.DEFAULT_AGENT_API_HOST}:${port}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAgentCreativeSelections = buildAgentCreativeSelections;
|
|
4
|
+
const promptOverrides_1 = require("../promptOverrides");
|
|
5
|
+
function buildAgentCreativeSelections(textApproaches, imageCounts, matrixEnabled, matrix, maxPerApproach = promptOverrides_1.MAX_IMAGES_PER_CREO_APPROACH) {
|
|
6
|
+
const selectedImageApproachIndices = imageCounts
|
|
7
|
+
.map((count, index) => (count > 0 ? index : -1))
|
|
8
|
+
.filter(index => index >= 0);
|
|
9
|
+
return {
|
|
10
|
+
textApproaches: {
|
|
11
|
+
selectedIndices: textApproaches,
|
|
12
|
+
selectedNumbers: textApproaches.map(index => index + 1),
|
|
13
|
+
},
|
|
14
|
+
imageApproaches: {
|
|
15
|
+
counts: imageCounts,
|
|
16
|
+
selectedIndices: selectedImageApproachIndices,
|
|
17
|
+
selectedNumbers: selectedImageApproachIndices.map(index => index + 1),
|
|
18
|
+
maxPerApproach,
|
|
19
|
+
},
|
|
20
|
+
matrix: {
|
|
21
|
+
enabled: matrixEnabled,
|
|
22
|
+
rows: matrix,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildEscalation = buildEscalation;
|
|
4
|
+
exports.shouldAttachEscalation = shouldAttachEscalation;
|
|
5
|
+
exports.buildValidationEscalation = buildValidationEscalation;
|
|
6
|
+
exports.inferEscalationFromError = inferEscalationFromError;
|
|
7
|
+
exports.enrichAgentFailure = enrichAgentFailure;
|
|
8
|
+
function buildEscalation(input) {
|
|
9
|
+
return input;
|
|
10
|
+
}
|
|
11
|
+
function shouldAttachEscalation(error) {
|
|
12
|
+
const lower = error.toLowerCase();
|
|
13
|
+
if (!lower.trim())
|
|
14
|
+
return false;
|
|
15
|
+
if (lower.includes('already running'))
|
|
16
|
+
return false;
|
|
17
|
+
if (lower.includes('retry shortly'))
|
|
18
|
+
return false;
|
|
19
|
+
if (lower.includes('unknown target'))
|
|
20
|
+
return false;
|
|
21
|
+
if (lower.includes('unknown job id'))
|
|
22
|
+
return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
function escalationFromCheckId(checkId) {
|
|
26
|
+
switch (checkId) {
|
|
27
|
+
case 'googleAuth':
|
|
28
|
+
return buildEscalation({
|
|
29
|
+
required: true,
|
|
30
|
+
kind: 'missing_credentials',
|
|
31
|
+
userMessage: 'Нужен вход в Google Drive в Комбайнере (Credentials → Login with Google). После входа повторите шаг агента.',
|
|
32
|
+
suggestedAction: 'open_credentials',
|
|
33
|
+
});
|
|
34
|
+
case 'openRouterKey':
|
|
35
|
+
return buildEscalation({
|
|
36
|
+
required: true,
|
|
37
|
+
kind: 'missing_credentials',
|
|
38
|
+
userMessage: 'В Комбайнере не указан API-ключ OpenRouter (Credentials). Добавьте ключ и повторите шаг агента.',
|
|
39
|
+
suggestedAction: 'open_credentials',
|
|
40
|
+
});
|
|
41
|
+
case 'driveFolder':
|
|
42
|
+
return buildEscalation({
|
|
43
|
+
required: true,
|
|
44
|
+
kind: 'needs_configuration',
|
|
45
|
+
userMessage: 'Не выбрана рабочая папка Google Drive. Выберите группу и папку в UI или вызовите setup-campaign через Agent API.',
|
|
46
|
+
suggestedAction: 'configure_session',
|
|
47
|
+
});
|
|
48
|
+
case 'product':
|
|
49
|
+
return buildEscalation({
|
|
50
|
+
required: true,
|
|
51
|
+
kind: 'needs_configuration',
|
|
52
|
+
userMessage: 'Не заполнено поле «Товар». Укажите product в setup-campaign / PATCH сессии или в UI, затем повторите генерацию.',
|
|
53
|
+
suggestedAction: 'configure_session',
|
|
54
|
+
});
|
|
55
|
+
case 'geoBlock':
|
|
56
|
+
return buildEscalation({
|
|
57
|
+
required: true,
|
|
58
|
+
kind: 'needs_configuration',
|
|
59
|
+
userMessage: 'Не заполнен блок гео (хотя бы одна строка). Передайте geo-block / generationMarkets или заполните в UI.',
|
|
60
|
+
suggestedAction: 'configure_session',
|
|
61
|
+
});
|
|
62
|
+
case 'creativeSelections':
|
|
63
|
+
return buildEscalation({
|
|
64
|
+
required: true,
|
|
65
|
+
kind: 'needs_configuration',
|
|
66
|
+
userMessage: 'Не выбраны креативные подходы (тексты и изображения). Настройте creative-selections / approach-matrix или UI.',
|
|
67
|
+
suggestedAction: 'configure_session',
|
|
68
|
+
});
|
|
69
|
+
case 'generatedPairsComplete':
|
|
70
|
+
return buildEscalation({
|
|
71
|
+
required: true,
|
|
72
|
+
kind: 'needs_manual_ui',
|
|
73
|
+
userMessage: 'Сгенерированные пары заголовок+текст неполные (например, есть текст без заголовка). В UI нажмите «Перегенерировать» у проблемной пары или заполните вручную, затем снова экспортируйте каталог. Агент: POST .../actions/regenerate-pair { "index": N }.',
|
|
74
|
+
suggestedAction: 'take_manual_control',
|
|
75
|
+
});
|
|
76
|
+
default:
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildValidationEscalation(input) {
|
|
81
|
+
if (input.blocking.length > 0) {
|
|
82
|
+
const primary = escalationFromCheckId(input.blocking[0].id);
|
|
83
|
+
if (primary) {
|
|
84
|
+
if (input.blocking.length === 1)
|
|
85
|
+
return primary;
|
|
86
|
+
const summary = input.blocking.map(check => check.message).join('; ');
|
|
87
|
+
return buildEscalation(Object.assign(Object.assign({}, primary), { userMessage: `Перед генерацией исправьте в Комбайнере: ${summary}` }));
|
|
88
|
+
}
|
|
89
|
+
return buildEscalation({
|
|
90
|
+
required: true,
|
|
91
|
+
kind: 'needs_configuration',
|
|
92
|
+
userMessage: `Перед генерацией исправьте в Комбайнере: ${input.blocking.map(check => check.message).join('; ')}`,
|
|
93
|
+
suggestedAction: 'configure_session',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (input.hints.length > 0) {
|
|
97
|
+
return buildEscalation({
|
|
98
|
+
required: false,
|
|
99
|
+
kind: 'needs_configuration',
|
|
100
|
+
userMessage: 'Оффер ещё не привязан в campaigns.csv. Привяжите оффер через setup-campaign или селектор в UI — иначе product/описание могут подтянуться неполностью.',
|
|
101
|
+
suggestedAction: 'configure_session',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
function inferEscalationFromError(error) {
|
|
107
|
+
const text = String(error !== null && error !== void 0 ? error : '').trim();
|
|
108
|
+
if (!text || !shouldAttachEscalation(text))
|
|
109
|
+
return null;
|
|
110
|
+
const lower = text.toLowerCase();
|
|
111
|
+
if (lower.includes('openrouter api key')) {
|
|
112
|
+
return buildEscalation({
|
|
113
|
+
required: true,
|
|
114
|
+
kind: 'missing_credentials',
|
|
115
|
+
userMessage: 'В Комбайнере не указан API-ключ OpenRouter (Credentials). Добавьте ключ и повторите шаг агента.',
|
|
116
|
+
suggestedAction: 'open_credentials',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (lower.includes('not logged in') ||
|
|
120
|
+
lower.includes('google drive auth') ||
|
|
121
|
+
lower.includes('google authentication') ||
|
|
122
|
+
lower.includes('valid token')) {
|
|
123
|
+
return buildEscalation({
|
|
124
|
+
required: true,
|
|
125
|
+
kind: 'missing_credentials',
|
|
126
|
+
userMessage: 'Нужен вход в Google Drive в Комбайнере (Credentials → Login with Google). После входа повторите шаг агента.',
|
|
127
|
+
suggestedAction: 'open_credentials',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (lower.includes('campaigns root folder is not configured')) {
|
|
131
|
+
return buildEscalation({
|
|
132
|
+
required: true,
|
|
133
|
+
kind: 'needs_configuration',
|
|
134
|
+
userMessage: 'В Credentials не указана «Папка с кампаниями». Задайте её в UI (Credentials) или откройте сессию, где она уже настроена.',
|
|
135
|
+
suggestedAction: 'open_credentials',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (lower.includes('working folder') || lower.includes('working drive folder')) {
|
|
139
|
+
return buildEscalation({
|
|
140
|
+
required: true,
|
|
141
|
+
kind: 'needs_configuration',
|
|
142
|
+
userMessage: 'Не выбрана рабочая папка Google Drive. Вызовите setup-campaign или выберите папку в UI.',
|
|
143
|
+
suggestedAction: 'configure_session',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (lower.includes('product.png') || lower.includes('product image')) {
|
|
147
|
+
return buildEscalation({
|
|
148
|
+
required: true,
|
|
149
|
+
kind: 'needs_drive_file',
|
|
150
|
+
userMessage: 'В рабочей папке нет product.png/jpg/webp. Загрузите файл на Drive, привяжите оффер в campaigns.csv или выберите оффер в UI.',
|
|
151
|
+
suggestedAction: 'upload_drive_file',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (lower.includes('agent handlers not ready') || lower.includes('agent api is not ready')) {
|
|
155
|
+
return buildEscalation({
|
|
156
|
+
required: true,
|
|
157
|
+
kind: 'session_not_ready',
|
|
158
|
+
userMessage: 'Сессия Комбайнера ещё загружается. Подождите несколько секунд и повторите запрос (GET /api/windows/{id}).',
|
|
159
|
+
suggestedAction: 'retry_later',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (lower.includes('no session with agent api ready')) {
|
|
163
|
+
return buildEscalation({
|
|
164
|
+
required: true,
|
|
165
|
+
kind: 'session_not_ready',
|
|
166
|
+
userMessage: 'Нет готовой сессии Комбайнера с Agent API. Откройте окно приложения или POST /api/windows и дождитесь ready.',
|
|
167
|
+
suggestedAction: 'retry_later',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (lower.includes('несколько изображений') ||
|
|
171
|
+
lower.includes('multiple images') ||
|
|
172
|
+
lower.includes('exactly one')) {
|
|
173
|
+
return buildEscalation({
|
|
174
|
+
required: true,
|
|
175
|
+
kind: 'needs_drive_file',
|
|
176
|
+
userMessage: 'В папке оффера на Drive должно быть ровно одно изображение продукта (любое имя файла). Удалите лишние картинки или объедините в один файл.',
|
|
177
|
+
suggestedAction: 'upload_drive_file',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (lower.includes('incomplete_generated_pairs') ||
|
|
181
|
+
lower.includes('catalog export blocked')) {
|
|
182
|
+
return buildEscalation({
|
|
183
|
+
required: true,
|
|
184
|
+
kind: 'needs_manual_ui',
|
|
185
|
+
userMessage: 'Каталог не собран: одна или несколько пар «заголовок + текст» неполные. Перегенерируйте проблемные пары в UI или через POST .../actions/regenerate-pair, затем повторите catalog.',
|
|
186
|
+
suggestedAction: 'take_manual_control',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (lower.includes('product is required') ||
|
|
190
|
+
lower.includes('at least one geo') ||
|
|
191
|
+
lower.includes('text creative approaches') ||
|
|
192
|
+
lower.includes('image creative approach') ||
|
|
193
|
+
lower.includes('link is required') ||
|
|
194
|
+
lower.includes('invalid link url') ||
|
|
195
|
+
lower.includes('groupname is required') ||
|
|
196
|
+
lower.includes('provide workspacefolderid') ||
|
|
197
|
+
lower.includes('select at least one geo')) {
|
|
198
|
+
return buildEscalation({
|
|
199
|
+
required: true,
|
|
200
|
+
kind: 'needs_configuration',
|
|
201
|
+
userMessage: `Агенту не хватает данных сессии: ${text}. Заполните поля через setup-campaign / PATCH / geo-block или в UI.`,
|
|
202
|
+
suggestedAction: 'configure_session',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return buildEscalation({
|
|
206
|
+
required: true,
|
|
207
|
+
kind: 'needs_configuration',
|
|
208
|
+
userMessage: `Агент не смог продолжить: ${text}. Проверьте сессию в UI или передайте оператору для эскалации в Комбайнер.`,
|
|
209
|
+
suggestedAction: 'configure_session',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function enrichAgentFailure(body) {
|
|
213
|
+
var _a, _b;
|
|
214
|
+
if (body.ok !== false)
|
|
215
|
+
return body;
|
|
216
|
+
if (body.escalation && typeof body.escalation === 'object')
|
|
217
|
+
return body;
|
|
218
|
+
const error = String((_b = (_a = body.error) !== null && _a !== void 0 ? _a : body.message) !== null && _b !== void 0 ? _b : '');
|
|
219
|
+
const escalation = inferEscalationFromError(error);
|
|
220
|
+
return escalation ? Object.assign(Object.assign({}, body), { escalation }) : body;
|
|
221
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findIncompleteGeneratedPairs = findIncompleteGeneratedPairs;
|
|
4
|
+
exports.shouldBlockCatalogForGeneratedPairs = shouldBlockCatalogForGeneratedPairs;
|
|
5
|
+
exports.formatIncompletePairsUserAlert = formatIncompletePairsUserAlert;
|
|
6
|
+
exports.buildCatalogBlockedByIncompletePairsFailure = buildCatalogBlockedByIncompletePairsFailure;
|
|
7
|
+
exports.buildIncompletePairsValidationMessage = buildIncompletePairsValidationMessage;
|
|
8
|
+
function findIncompleteGeneratedPairs(titles, texts) {
|
|
9
|
+
var _a, _b, _c, _d;
|
|
10
|
+
const len = Math.max(titles.length, texts.length);
|
|
11
|
+
if (len === 0)
|
|
12
|
+
return [];
|
|
13
|
+
const incomplete = [];
|
|
14
|
+
for (let pairIndex = 0; pairIndex < len; pairIndex++) {
|
|
15
|
+
const titleData = titles[pairIndex];
|
|
16
|
+
const textData = texts[pairIndex];
|
|
17
|
+
const generating = Boolean((titleData === null || titleData === void 0 ? void 0 : titleData.generating) || (textData === null || textData === void 0 ? void 0 : textData.generating));
|
|
18
|
+
const title = String((_a = titleData === null || titleData === void 0 ? void 0 : titleData.title) !== null && _a !== void 0 ? _a : '').trim();
|
|
19
|
+
const text = String((_b = textData === null || textData === void 0 ? void 0 : textData.text) !== null && _b !== void 0 ? _b : '').trim();
|
|
20
|
+
const missingTitle = generating || !title || Boolean(titleData === null || titleData === void 0 ? void 0 : titleData.failed);
|
|
21
|
+
const missingText = generating || !text || Boolean(textData === null || textData === void 0 ? void 0 : textData.failed);
|
|
22
|
+
if (missingTitle || missingText) {
|
|
23
|
+
incomplete.push({
|
|
24
|
+
index: (_d = (_c = titleData === null || titleData === void 0 ? void 0 : titleData.index) !== null && _c !== void 0 ? _c : textData === null || textData === void 0 ? void 0 : textData.index) !== null && _d !== void 0 ? _d : pairIndex + 1,
|
|
25
|
+
pairIndex,
|
|
26
|
+
geo: (titleData === null || titleData === void 0 ? void 0 : titleData.geo) || (textData === null || textData === void 0 ? void 0 : textData.geo),
|
|
27
|
+
missingTitle,
|
|
28
|
+
missingText,
|
|
29
|
+
generating,
|
|
30
|
+
titleError: titleData === null || titleData === void 0 ? void 0 : titleData.errorMessage,
|
|
31
|
+
textError: textData === null || textData === void 0 ? void 0 : textData.errorMessage,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return incomplete;
|
|
36
|
+
}
|
|
37
|
+
function shouldBlockCatalogForGeneratedPairs(input) {
|
|
38
|
+
const hasGeneratedStructure = input.titles.length > 0 || input.texts.length > 0;
|
|
39
|
+
if (!hasGeneratedStructure) {
|
|
40
|
+
return { block: false, incomplete: [] };
|
|
41
|
+
}
|
|
42
|
+
const incomplete = findIncompleteGeneratedPairs(input.titles, input.texts);
|
|
43
|
+
return { block: incomplete.length > 0, incomplete };
|
|
44
|
+
}
|
|
45
|
+
function describeIncompletePair(pair) {
|
|
46
|
+
const geoSuffix = pair.geo ? ` (${pair.geo})` : '';
|
|
47
|
+
if (pair.generating) {
|
|
48
|
+
return `пара ${pair.index}${geoSuffix} — генерация ещё идёт`;
|
|
49
|
+
}
|
|
50
|
+
if (pair.missingTitle && pair.missingText) {
|
|
51
|
+
return `пара ${pair.index}${geoSuffix} — заголовок и текст не получены`;
|
|
52
|
+
}
|
|
53
|
+
if (pair.missingTitle) {
|
|
54
|
+
return `пара ${pair.index}${geoSuffix} — заголовок не получен`;
|
|
55
|
+
}
|
|
56
|
+
return `пара ${pair.index}${geoSuffix} — текст не получен`;
|
|
57
|
+
}
|
|
58
|
+
function formatIncompletePairsUserAlert(pairs) {
|
|
59
|
+
const details = pairs.map(describeIncompletePair).join('; ');
|
|
60
|
+
return `Нельзя собрать каталог: ${details}. Перегенерируйте проблемные пары (кнопка «Перегенерировать») или заполните поля вручную.`;
|
|
61
|
+
}
|
|
62
|
+
function buildCatalogBlockedByIncompletePairsFailure(pairs) {
|
|
63
|
+
const summary = pairs
|
|
64
|
+
.map(pair => {
|
|
65
|
+
const geoSuffix = pair.geo ? ` geo=${pair.geo}` : '';
|
|
66
|
+
if (pair.generating)
|
|
67
|
+
return `pair ${pair.index}${geoSuffix}: generation in progress`;
|
|
68
|
+
if (pair.missingTitle && pair.missingText)
|
|
69
|
+
return `pair ${pair.index}${geoSuffix}: title and text missing`;
|
|
70
|
+
if (pair.missingTitle)
|
|
71
|
+
return `pair ${pair.index}${geoSuffix}: title missing`;
|
|
72
|
+
return `pair ${pair.index}${geoSuffix}: text missing`;
|
|
73
|
+
})
|
|
74
|
+
.join('; ');
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `Catalog export blocked: incomplete generated pairs — ${summary}. Regenerate each pair via POST .../actions/regenerate-pair with { "index": N } (1-based pair number), or PATCH .../generated.`,
|
|
78
|
+
code: 'incomplete_generated_pairs',
|
|
79
|
+
incompletePairs: pairs,
|
|
80
|
+
action: 'regeneratePair',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function buildIncompletePairsValidationMessage(pairs) {
|
|
84
|
+
return pairs.map(describeIncompletePair).join('; ');
|
|
85
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sendAgentJsonFailure = sendAgentJsonFailure;
|
|
13
|
+
exports.sendAgentActionResponse = sendAgentActionResponse;
|
|
14
|
+
exports.waitForAgentJobStatus = waitForAgentJobStatus;
|
|
15
|
+
const escalation_1 = require("./escalation");
|
|
16
|
+
const jobStatus_1 = require("./jobStatus");
|
|
17
|
+
function sendAgentJsonFailure(res, status, body) {
|
|
18
|
+
res.status(status).json((0, escalation_1.enrichAgentFailure)(body));
|
|
19
|
+
}
|
|
20
|
+
function sendAgentActionResponse(res, response) {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
sendAgentJsonFailure(res, response.status, { ok: false, error: response.error });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (((_a = response.result) === null || _a === void 0 ? void 0 : _a.ok) === false) {
|
|
27
|
+
sendAgentJsonFailure(res, 400, response.result);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (((_b = response.result) === null || _b === void 0 ? void 0 : _b.async) === true) {
|
|
31
|
+
res.status(202).json(response.result);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
res.json(response.result);
|
|
35
|
+
}
|
|
36
|
+
function waitForAgentJobStatus(callAgentRendererApi, sessionId, jobId, timeoutMs) {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
const startedAt = Date.now();
|
|
40
|
+
let lastStatus = 'idle';
|
|
41
|
+
let initialRunId = null;
|
|
42
|
+
let sawRunning = false;
|
|
43
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
44
|
+
const response = yield callAgentRendererApi(sessionId, 'getJobStatus', { jobId });
|
|
45
|
+
if (!response.ok)
|
|
46
|
+
return response;
|
|
47
|
+
const job = (_a = response.result) === null || _a === void 0 ? void 0 : _a.job;
|
|
48
|
+
const status = String((_b = job === null || job === void 0 ? void 0 : job.status) !== null && _b !== void 0 ? _b : 'idle');
|
|
49
|
+
const runId = typeof (job === null || job === void 0 ? void 0 : job.runId) === 'number' ? job.runId : undefined;
|
|
50
|
+
if (initialRunId === null && typeof runId === 'number') {
|
|
51
|
+
initialRunId = runId;
|
|
52
|
+
}
|
|
53
|
+
lastStatus = status;
|
|
54
|
+
if (status === 'running') {
|
|
55
|
+
sawRunning = true;
|
|
56
|
+
}
|
|
57
|
+
if ((0, jobStatus_1.isTerminalAgentJobWait)({
|
|
58
|
+
jobId,
|
|
59
|
+
status,
|
|
60
|
+
runId,
|
|
61
|
+
initialRunId,
|
|
62
|
+
sawRunning,
|
|
63
|
+
})) {
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
result: {
|
|
71
|
+
ok: true,
|
|
72
|
+
job: { id: jobId, status: lastStatus, timedOut: true },
|
|
73
|
+
timedOut: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildImageBlockingFailureMessage = buildImageBlockingFailureMessage;
|
|
4
|
+
exports.isImageSlotFailedWithoutUpload = isImageSlotFailedWithoutUpload;
|
|
5
|
+
exports.isImageBlockingFailure = isImageBlockingFailure;
|
|
6
|
+
exports.buildImagesReadiness = buildImagesReadiness;
|
|
7
|
+
const regenerateImages_1 = require("./regenerateImages");
|
|
8
|
+
function buildImageBlockingFailureMessage(img) {
|
|
9
|
+
var _a, _b, _c, _d, _e;
|
|
10
|
+
if ((0, regenerateImages_1.isImageSlotCheckTransportFailure)(img)) {
|
|
11
|
+
const detail = ((_a = img.errorMessage) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = img.checkResult) === null || _b === void 0 ? void 0 : _b.trim());
|
|
12
|
+
return detail
|
|
13
|
+
? `Validator transport error (recheck, do not rebuild): ${detail}`
|
|
14
|
+
: 'Validator transport error (recheck, do not rebuild)';
|
|
15
|
+
}
|
|
16
|
+
if ((_c = img.errorMessage) === null || _c === void 0 ? void 0 : _c.trim())
|
|
17
|
+
return img.errorMessage.trim();
|
|
18
|
+
if (img.checkStatus === 'needs_rebuild' && ((_d = img.checkResult) === null || _d === void 0 ? void 0 : _d.trim()))
|
|
19
|
+
return img.checkResult.trim();
|
|
20
|
+
if (img.checkStatus === 'needs_rebuild')
|
|
21
|
+
return 'Validator: needs_rebuild';
|
|
22
|
+
if (!((_e = img.imageUrl) === null || _e === void 0 ? void 0 : _e.trim()))
|
|
23
|
+
return 'No image generated';
|
|
24
|
+
return 'Image slot incomplete';
|
|
25
|
+
}
|
|
26
|
+
/** Terminal problem slot that is not uploaded to Drive yet. */
|
|
27
|
+
function isImageSlotFailedWithoutUpload(img) {
|
|
28
|
+
if (img.uploaded)
|
|
29
|
+
return false;
|
|
30
|
+
if ((0, regenerateImages_1.isImageSlotBusy)(img))
|
|
31
|
+
return false;
|
|
32
|
+
return (0, regenerateImages_1.isImageSlotFailed)(img) || img.checkStatus === 'needs_rebuild';
|
|
33
|
+
}
|
|
34
|
+
function isImageBlockingFailure(img) {
|
|
35
|
+
if ((0, regenerateImages_1.isImageSlotBusy)(img))
|
|
36
|
+
return false;
|
|
37
|
+
if ((0, regenerateImages_1.isImageSlotUploadedOk)(img))
|
|
38
|
+
return false;
|
|
39
|
+
if (isImageSlotFailedWithoutUpload(img))
|
|
40
|
+
return true;
|
|
41
|
+
if ((0, regenerateImages_1.isImageSlotPendingUpload)(img))
|
|
42
|
+
return false;
|
|
43
|
+
return (0, regenerateImages_1.isImageSlotFailed)(img);
|
|
44
|
+
}
|
|
45
|
+
function buildImagesReadiness(images, options) {
|
|
46
|
+
const total = images.length;
|
|
47
|
+
const uploaded = images.filter(img => img.uploaded).length;
|
|
48
|
+
const failedWithoutUpload = images.filter(isImageSlotFailedWithoutUpload);
|
|
49
|
+
const blockingFailures = images.filter(isImageBlockingFailure).map(img => ({
|
|
50
|
+
index: img.index,
|
|
51
|
+
errorMessage: buildImageBlockingFailureMessage(img),
|
|
52
|
+
checkFailed: Boolean(img.checkFailed),
|
|
53
|
+
recommendedAction: (0, regenerateImages_1.resolveImageSlotFailureAction)(img),
|
|
54
|
+
}));
|
|
55
|
+
const imagesReady = total > 0 &&
|
|
56
|
+
!(options === null || options === void 0 ? void 0 : options.busy) &&
|
|
57
|
+
uploaded === total &&
|
|
58
|
+
failedWithoutUpload.length === 0;
|
|
59
|
+
return { imagesReady, blockingFailures, uploaded, total };
|
|
60
|
+
}
|