docs-combiner 0.2.2 → 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.
Files changed (53) hide show
  1. package/dist/agentApi/agentControlClaim.js +26 -0
  2. package/dist/agentApi/approachMatrixView.js +19 -0
  3. package/dist/agentApi/capabilitiesMeta.js +78 -0
  4. package/dist/agentApi/catalogUrlPresets.js +26 -0
  5. package/dist/agentApi/constants.js +11 -0
  6. package/dist/agentApi/creativeSelections.js +25 -0
  7. package/dist/agentApi/escalation.js +221 -0
  8. package/dist/agentApi/generatedPairsGuard.js +85 -0
  9. package/dist/agentApi/httpHelpers.js +77 -0
  10. package/dist/agentApi/imagesReadiness.js +60 -0
  11. package/dist/agentApi/jobStatus.js +113 -0
  12. package/dist/agentApi/productImageDrive.js +39 -0
  13. package/dist/agentApi/regenerateImages.js +125 -0
  14. package/dist/agentApi/rendererTypes.js +3 -0
  15. package/dist/agentApi/routes.js +440 -0
  16. package/dist/agentApi/sessionSnapshot.js +82 -0
  17. package/dist/agentApi/sessionTypes.js +2 -0
  18. package/dist/agentApi/spec.js +1045 -0
  19. package/dist/agentApi/types.js +2 -0
  20. package/dist/agentApi/validation.js +32 -0
  21. package/dist/campaignsCsv.js +420 -0
  22. package/dist/contentPairs.js +62 -0
  23. package/dist/flexcardBalance.js +88 -0
  24. package/dist/integrations/driveApi.js +420 -0
  25. package/dist/integrations/httpTimeout.js +116 -0
  26. package/dist/integrations/openrouter.js +532 -0
  27. package/dist/main.js +830 -165
  28. package/dist/models.js +17 -0
  29. package/dist/offerSettings.js +19 -0
  30. package/dist/preload.js +28 -0
  31. package/dist/promptOverrides.js +437 -0
  32. package/dist/prompts.js +1243 -0
  33. package/dist/renderer.js +19 -19
  34. package/dist/renderer.js.LICENSE.txt +4 -0
  35. package/dist/renderer.js.map +1 -1
  36. package/dist/server/app.js +378 -0
  37. package/dist/server/auth.js +74 -0
  38. package/dist/server/contentJobs.js +367 -0
  39. package/dist/server/driveCatalog.js +122 -0
  40. package/dist/server/driveProxy.js +220 -0
  41. package/dist/server/flexcardMeta.js +29 -0
  42. package/dist/server/googleAuth.js +84 -0
  43. package/dist/server/imageAssets.js +87 -0
  44. package/dist/server/imageJobs.js +776 -0
  45. package/dist/server/index.js +38 -0
  46. package/dist/server/jobEvents.js +116 -0
  47. package/dist/server/jobs.js +358 -0
  48. package/dist/server/openRouterMeta.js +58 -0
  49. package/dist/server/spec.js +544 -0
  50. package/dist/server/storage.js +133 -0
  51. package/dist/server/telegram.js +53 -0
  52. package/dist/server/workspaceSnapshot.js +273 -0
  53. package/package.json +18 -4
@@ -0,0 +1,1045 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAgentApiSpec = getAgentApiSpec;
4
+ /** OpenAPI-like description for GET /api/spec */
5
+ function getAgentApiSpec(host, port, version) {
6
+ return {
7
+ openapi: '3.1.0',
8
+ info: {
9
+ title: 'Docs Combiner Agent API',
10
+ version,
11
+ description: 'Local-only API for automation agents controlling Docs Combiner.',
12
+ },
13
+ servers: [
14
+ {
15
+ url: `http://${host}:${port}`,
16
+ description: 'Loopback-only Electron main process API',
17
+ },
18
+ ],
19
+ paths: {
20
+ '/api/windows': {
21
+ get: {
22
+ operationId: 'listWindows',
23
+ summary: 'List open Docs Combiner work sessions',
24
+ responses: {
25
+ '200': {
26
+ description: 'Current work sessions available to an automation agent',
27
+ content: {
28
+ 'application/json': {
29
+ schema: {
30
+ type: 'object',
31
+ required: ['ok', 'windows'],
32
+ properties: {
33
+ ok: { type: 'boolean', const: true },
34
+ windows: {
35
+ type: 'array',
36
+ items: { $ref: '#/components/schemas/AgentWorkSessionInfo' },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ },
45
+ post: {
46
+ operationId: 'createWindow',
47
+ summary: 'Open a new Docs Combiner work session (Electron window)',
48
+ requestBody: {
49
+ required: false,
50
+ content: {
51
+ 'application/json': {
52
+ schema: {
53
+ type: 'object',
54
+ properties: {
55
+ workspaceId: {
56
+ type: 'string',
57
+ description: 'Optional stable session id. Generated if omitted.',
58
+ },
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ responses: {
65
+ '201': {
66
+ description: 'New session created and Agent API is ready in the renderer',
67
+ },
68
+ '202': {
69
+ description: 'New window opened, but renderer Agent API is not ready yet',
70
+ },
71
+ },
72
+ },
73
+ },
74
+ '/api/catalog': {
75
+ get: {
76
+ operationId: 'getDriveCatalog',
77
+ summary: 'List offers, campaign groups and workspace folders from Google Drive',
78
+ parameters: [
79
+ {
80
+ name: 'sessionId',
81
+ in: 'query',
82
+ required: false,
83
+ schema: { type: 'string' },
84
+ description: 'Prefer data from this session if available.',
85
+ },
86
+ ],
87
+ responses: {
88
+ '200': { description: 'Drive catalog snapshot' },
89
+ '503': { description: 'No session with Agent API ready' },
90
+ },
91
+ },
92
+ },
93
+ '/api/windows/{sessionId}': {
94
+ get: {
95
+ operationId: 'getSessionInfo',
96
+ summary: 'Return all agent-visible information from one work session',
97
+ parameters: [
98
+ {
99
+ name: 'sessionId',
100
+ in: 'path',
101
+ required: true,
102
+ schema: { type: 'string' },
103
+ },
104
+ ],
105
+ responses: {
106
+ '200': {
107
+ description: 'Current live session state',
108
+ content: {
109
+ 'application/json': {
110
+ schema: {
111
+ type: 'object',
112
+ required: ['ok', 'session'],
113
+ properties: {
114
+ ok: { type: 'boolean', const: true },
115
+ session: { $ref: '#/components/schemas/AgentSessionInfo' },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ },
121
+ '404': { description: 'Session not found' },
122
+ '409': { description: 'Session renderer is not ready for agent calls' },
123
+ },
124
+ },
125
+ delete: {
126
+ operationId: 'closeSession',
127
+ summary: 'Close one work session window',
128
+ parameters: [
129
+ {
130
+ name: 'sessionId',
131
+ in: 'path',
132
+ required: true,
133
+ schema: { type: 'string' },
134
+ },
135
+ ],
136
+ responses: {
137
+ '200': {
138
+ description: 'Session window closed',
139
+ content: {
140
+ 'application/json': {
141
+ schema: {
142
+ type: 'object',
143
+ required: ['ok', 'closed'],
144
+ properties: {
145
+ ok: { type: 'boolean', const: true },
146
+ closed: { type: 'string' },
147
+ },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ '404': { description: 'Session not found' },
153
+ },
154
+ },
155
+ patch: {
156
+ operationId: 'patchSessionInfo',
157
+ summary: 'Partially update one work session without touching omitted fields',
158
+ parameters: [
159
+ {
160
+ name: 'sessionId',
161
+ in: 'path',
162
+ required: true,
163
+ schema: { type: 'string' },
164
+ },
165
+ ],
166
+ requestBody: {
167
+ required: true,
168
+ content: {
169
+ 'application/json': {
170
+ schema: { $ref: '#/components/schemas/PatchSessionInfoRequest' },
171
+ examples: {
172
+ productOnly: {
173
+ value: { generation: { product: 'Detoxil Water' } },
174
+ },
175
+ firstGeoOnly: {
176
+ value: { generation: { firstGeo: 'Romania' } },
177
+ },
178
+ geoPriceLinkPartial: {
179
+ value: {
180
+ generation: {
181
+ geoBlock: [
182
+ { id: 'market-1', link: 'https://example.com/ro/' },
183
+ ],
184
+ },
185
+ },
186
+ },
187
+ creativeSelectionsOnly: {
188
+ value: {
189
+ creativeSelections: {
190
+ selectedTextApproachNumbers: [1, 2, 3],
191
+ },
192
+ },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ responses: {
199
+ '200': {
200
+ description: 'Session was partially updated',
201
+ content: {
202
+ 'application/json': {
203
+ schema: {
204
+ type: 'object',
205
+ required: ['ok', 'session'],
206
+ properties: {
207
+ ok: { type: 'boolean', const: true },
208
+ session: { $ref: '#/components/schemas/AgentSessionInfo' },
209
+ },
210
+ },
211
+ },
212
+ },
213
+ },
214
+ '400': { description: 'Invalid partial update payload' },
215
+ '404': { description: 'Session not found' },
216
+ '409': { description: 'Session renderer is not ready for agent calls' },
217
+ },
218
+ },
219
+ },
220
+ '/api/windows/{sessionId}/catalog/groups': {
221
+ post: {
222
+ operationId: 'createCampaignGroup',
223
+ summary: 'Create a campaign group folder on Google Drive (same as UI «Создать» for group)',
224
+ parameters: [
225
+ { name: 'sessionId', in: 'path', required: true, schema: { type: 'string' } },
226
+ ],
227
+ requestBody: {
228
+ required: true,
229
+ content: {
230
+ 'application/json': {
231
+ schema: {
232
+ type: 'object',
233
+ required: ['name'],
234
+ properties: {
235
+ name: { type: 'string', description: 'Group folder name under campaigns root' },
236
+ },
237
+ },
238
+ },
239
+ },
240
+ },
241
+ responses: {
242
+ '200': { description: 'Group folder created' },
243
+ '400': { description: 'Invalid payload or group already exists' },
244
+ '404': { description: 'Session not found' },
245
+ '409': { description: 'Session renderer is not ready for agent calls' },
246
+ },
247
+ },
248
+ },
249
+ '/api/windows/{sessionId}/catalog/workspaces': {
250
+ post: {
251
+ operationId: 'createWorkspaceFolder',
252
+ summary: 'Create a workspace folder inside a campaign group on Google Drive',
253
+ parameters: [
254
+ { name: 'sessionId', in: 'path', required: true, schema: { type: 'string' } },
255
+ ],
256
+ requestBody: {
257
+ required: true,
258
+ content: {
259
+ 'application/json': {
260
+ schema: {
261
+ type: 'object',
262
+ required: ['groupName', 'name'],
263
+ properties: {
264
+ groupName: { type: 'string' },
265
+ name: { type: 'string', description: 'Workspace folder name' },
266
+ select: {
267
+ type: 'boolean',
268
+ default: true,
269
+ description: 'Select the new workspace in this session after creation',
270
+ },
271
+ },
272
+ },
273
+ },
274
+ },
275
+ },
276
+ responses: {
277
+ '200': { description: 'Workspace folder created' },
278
+ '400': { description: 'Invalid payload or folder already exists' },
279
+ '404': { description: 'Session not found' },
280
+ '409': { description: 'Session renderer is not ready for agent calls' },
281
+ },
282
+ },
283
+ },
284
+ '/api/windows/{sessionId}/generate': {
285
+ post: {
286
+ operationId: 'startGeneration',
287
+ summary: 'Start content, image or catalog generation (same as UI buttons)',
288
+ parameters: [
289
+ { name: 'sessionId', in: 'path', required: true, schema: { type: 'string' } },
290
+ ],
291
+ requestBody: {
292
+ required: false,
293
+ content: {
294
+ 'application/json': {
295
+ schema: {
296
+ type: 'object',
297
+ properties: {
298
+ targets: {
299
+ type: 'array',
300
+ items: {
301
+ type: 'string',
302
+ enum: ['content', 'images', 'catalog', 'all'],
303
+ },
304
+ description: 'Sequential targets. Default: ["content"]. "images" creates the initial image slot matrix only; after slots exist it is blocked unless force:true is passed, otherwise use regenerate-images with explicit indices.',
305
+ },
306
+ async: {
307
+ type: 'boolean',
308
+ description: 'If true, return 202 immediately and poll jobs for completion.',
309
+ },
310
+ force: {
311
+ type: 'boolean',
312
+ description: 'If true, allow a full "images" run even when image slots already exist. This overwrites the entire existing batch (including OK/uploaded slots). Without it, full image generation is blocked once slots exist.',
313
+ },
314
+ },
315
+ },
316
+ examples: {
317
+ contentAndImages: {
318
+ value: { targets: ['content', 'images'] },
319
+ },
320
+ fullPipeline: {
321
+ value: { targets: ['content', 'images', 'catalog'] },
322
+ },
323
+ forceFullImageRebuild: {
324
+ value: { targets: ['images'], force: true },
325
+ },
326
+ },
327
+ },
328
+ },
329
+ },
330
+ responses: {
331
+ '200': { description: 'Generation started or completed for requested targets' },
332
+ '202': { description: 'Async generation accepted; poll jobs endpoints' },
333
+ '400': { description: 'Validation failed or unknown target' },
334
+ '404': { description: 'Session not found' },
335
+ '409': { description: 'Session renderer is not ready or job already running' },
336
+ },
337
+ },
338
+ },
339
+ '/api/windows/{sessionId}/setup-campaign': {
340
+ post: {
341
+ operationId: 'setupCampaignWorkspace',
342
+ summary: 'Select campaign group, workspace folder, offer binding and optional generation fields',
343
+ parameters: [
344
+ {
345
+ name: 'sessionId',
346
+ in: 'path',
347
+ required: true,
348
+ schema: { type: 'string' },
349
+ },
350
+ ],
351
+ requestBody: {
352
+ required: true,
353
+ content: {
354
+ 'application/json': {
355
+ schema: { $ref: '#/components/schemas/SetupCampaignWorkspaceRequest' },
356
+ },
357
+ },
358
+ },
359
+ responses: {
360
+ '200': { description: 'Campaign workspace configured' },
361
+ '400': { description: 'Invalid payload or workspace not found' },
362
+ '404': { description: 'Session not found' },
363
+ '409': { description: 'Session renderer is not ready for agent calls' },
364
+ },
365
+ },
366
+ },
367
+ '/api/windows/{sessionId}/geo-block': {
368
+ post: {
369
+ operationId: 'fillGeoBlock',
370
+ summary: 'Update the geo + price + link block in one work session',
371
+ description: 'A single object partially updates one row and preserves omitted fields. A markets array sets the exact row list, removing rows not present in the array; omitted fields inside each row are preserved by id/index when possible.',
372
+ parameters: [
373
+ {
374
+ name: 'sessionId',
375
+ in: 'path',
376
+ required: true,
377
+ schema: { type: 'string' },
378
+ },
379
+ ],
380
+ requestBody: {
381
+ required: true,
382
+ content: {
383
+ 'application/json': {
384
+ schema: { $ref: '#/components/schemas/FillGeoBlockRequest' },
385
+ examples: {
386
+ singleMarket: {
387
+ value: {
388
+ geo: 'Romania',
389
+ priceWithCurrency: '149 RON',
390
+ link: 'https://example.com/product/',
391
+ },
392
+ },
393
+ multipleMarkets: {
394
+ value: {
395
+ markets: [
396
+ {
397
+ geo: 'Romania',
398
+ priceWithCurrency: '149 RON',
399
+ link: 'https://example.com/ro/',
400
+ },
401
+ {
402
+ geo: 'Hungary',
403
+ priceWithCurrency: '11400 HUF',
404
+ link: 'https://example.com/hu/',
405
+ },
406
+ ],
407
+ },
408
+ },
409
+ },
410
+ },
411
+ },
412
+ },
413
+ responses: {
414
+ '200': {
415
+ description: 'Geo block was replaced',
416
+ content: {
417
+ 'application/json': {
418
+ schema: {
419
+ type: 'object',
420
+ required: ['ok', 'markets'],
421
+ properties: {
422
+ ok: { type: 'boolean', const: true },
423
+ markets: {
424
+ type: 'array',
425
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
426
+ },
427
+ },
428
+ },
429
+ },
430
+ },
431
+ },
432
+ '400': { description: 'Invalid geo block payload' },
433
+ '404': { description: 'Session not found' },
434
+ '409': { description: 'Session renderer is not ready for agent calls' },
435
+ },
436
+ },
437
+ },
438
+ '/api/windows/{sessionId}/creative-selections': {
439
+ get: {
440
+ operationId: 'getCreativeSelections',
441
+ summary: 'Return selected text and image creative approaches for one work session',
442
+ parameters: [
443
+ {
444
+ name: 'sessionId',
445
+ in: 'path',
446
+ required: true,
447
+ schema: { type: 'string' },
448
+ },
449
+ ],
450
+ responses: {
451
+ '200': {
452
+ description: 'Current creative approach selections',
453
+ content: {
454
+ 'application/json': {
455
+ schema: {
456
+ type: 'object',
457
+ required: ['ok', 'selections'],
458
+ properties: {
459
+ ok: { type: 'boolean', const: true },
460
+ selections: { $ref: '#/components/schemas/CreativeSelections' },
461
+ },
462
+ },
463
+ },
464
+ },
465
+ },
466
+ '404': { description: 'Session not found' },
467
+ '409': { description: 'Session renderer is not ready for agent calls' },
468
+ },
469
+ },
470
+ post: {
471
+ operationId: 'setCreativeSelections',
472
+ summary: 'Set selected text and image creative approaches for one work session',
473
+ parameters: [
474
+ {
475
+ name: 'sessionId',
476
+ in: 'path',
477
+ required: true,
478
+ schema: { type: 'string' },
479
+ },
480
+ ],
481
+ requestBody: {
482
+ required: true,
483
+ content: {
484
+ 'application/json': {
485
+ schema: { $ref: '#/components/schemas/SetCreativeSelectionsRequest' },
486
+ examples: {
487
+ uiNumbers: {
488
+ value: {
489
+ selectedTextApproachNumbers: [1, 2, 3],
490
+ imageApproachCounts: [1, 1, 0, 0, 2, 0, 0, 0, 0, 0],
491
+ },
492
+ },
493
+ imageNumbers: {
494
+ value: {
495
+ selectedTextApproachIndices: [0, 1, 2],
496
+ selectedImageApproachNumbers: [1, 5],
497
+ },
498
+ },
499
+ },
500
+ },
501
+ },
502
+ },
503
+ responses: {
504
+ '200': {
505
+ description: 'Creative approach selections were updated',
506
+ content: {
507
+ 'application/json': {
508
+ schema: {
509
+ type: 'object',
510
+ required: ['ok', 'selections'],
511
+ properties: {
512
+ ok: { type: 'boolean', const: true },
513
+ selections: { $ref: '#/components/schemas/CreativeSelections' },
514
+ },
515
+ },
516
+ },
517
+ },
518
+ },
519
+ '400': { description: 'Invalid creative selections payload' },
520
+ '404': { description: 'Session not found' },
521
+ '409': { description: 'Session renderer is not ready for agent calls' },
522
+ },
523
+ },
524
+ },
525
+ '/api/windows/{sessionId}/capabilities': {
526
+ get: {
527
+ operationId: 'getCapabilities',
528
+ summary: 'Return what the agent is allowed to read or change in this session',
529
+ responses: { '200': { description: 'Session capabilities' } },
530
+ },
531
+ },
532
+ '/api/windows/{sessionId}/validation': {
533
+ get: {
534
+ operationId: 'getValidation',
535
+ summary: 'Return readiness checks and blocking issues for this session',
536
+ responses: { '200': { description: 'Validation checklist' } },
537
+ },
538
+ },
539
+ '/api/windows/{sessionId}/jobs': {
540
+ get: {
541
+ operationId: 'listJobs',
542
+ summary: 'Return observable UI job statuses for this session',
543
+ responses: { '200': { description: 'Known job statuses' } },
544
+ },
545
+ },
546
+ '/api/windows/{sessionId}/jobs/{jobId}': {
547
+ get: {
548
+ operationId: 'getJobStatus',
549
+ summary: 'Return one observable UI job status',
550
+ parameters: [
551
+ { name: 'sessionId', in: 'path', required: true, schema: { type: 'string' } },
552
+ { name: 'jobId', in: 'path', required: true, schema: { type: 'string' } },
553
+ { name: 'wait', in: 'query', required: false, schema: { type: 'string', enum: ['1', 'true'] } },
554
+ { name: 'timeout', in: 'query', required: false, schema: { type: 'integer', minimum: 1, maximum: 600 } },
555
+ ],
556
+ responses: { '200': { description: 'Job status' }, '404': { description: 'Unknown job id' } },
557
+ },
558
+ },
559
+ '/api/windows/{sessionId}/approach-matrix': {
560
+ get: {
561
+ operationId: 'getApproachMatrix',
562
+ summary: 'Return creative approach matrix for one work session',
563
+ responses: { '200': { description: 'Approach matrix snapshot' } },
564
+ },
565
+ post: {
566
+ operationId: 'setApproachMatrix',
567
+ summary: 'Set creative approach matrix for one work session',
568
+ responses: { '200': { description: 'Approach matrix updated' }, '400': { description: 'Invalid matrix payload' } },
569
+ },
570
+ },
571
+ '/api/windows/{sessionId}/agent-control': {
572
+ get: {
573
+ operationId: 'getAgentControlStatus',
574
+ summary: 'Return whether this session is agent-controlled or manual',
575
+ responses: { '200': { description: 'Agent control mode' } },
576
+ },
577
+ post: {
578
+ operationId: 'setAgentControlMode',
579
+ summary: 'Switch agent control mode for this session window',
580
+ responses: { '200': { description: 'Mode updated' }, '400': { description: 'Invalid mode' } },
581
+ },
582
+ },
583
+ '/api/windows/{sessionId}/actions/load-content': {
584
+ post: {
585
+ operationId: 'loadContent',
586
+ summary: 'Load project-settings and generated artifacts from Drive',
587
+ responses: { '200': { description: 'Load completed' }, '202': { description: 'Async load accepted' } },
588
+ },
589
+ },
590
+ '/api/windows/{sessionId}/actions/upload-images': {
591
+ post: {
592
+ operationId: 'uploadImages',
593
+ summary: 'Upload pending creative images to Drive',
594
+ responses: { '200': { description: 'Upload completed' }, '202': { description: 'Async upload accepted' } },
595
+ },
596
+ },
597
+ '/api/windows/{sessionId}/actions/check-images': {
598
+ post: {
599
+ operationId: 'checkImages',
600
+ summary: 'Re-run validator on existing creative images (no new generation; manual/agent only)',
601
+ responses: { '200': { description: 'Check completed' }, '202': { description: 'Async check accepted' } },
602
+ },
603
+ },
604
+ '/api/windows/{sessionId}/actions/regenerate-pair': {
605
+ post: {
606
+ operationId: 'regeneratePair',
607
+ summary: 'Regenerate one title+text pair (same geo and approach as in UI)',
608
+ requestBody: {
609
+ required: true,
610
+ content: {
611
+ 'application/json': {
612
+ schema: {
613
+ type: 'object',
614
+ required: ['index'],
615
+ properties: {
616
+ index: { type: 'integer', minimum: 1, description: '1-based pair number' },
617
+ },
618
+ },
619
+ examples: {
620
+ pair1: { value: { index: 1 } },
621
+ },
622
+ },
623
+ },
624
+ },
625
+ responses: {
626
+ '200': { description: 'Pair regenerated successfully' },
627
+ '400': { description: 'Invalid index or pair still incomplete after retries' },
628
+ },
629
+ },
630
+ },
631
+ '/api/windows/{sessionId}/actions/regenerate-images': {
632
+ post: {
633
+ operationId: 'regenerateImages',
634
+ summary: 'Regenerate selected creative image slots without resetting the full matrix',
635
+ requestBody: {
636
+ required: true,
637
+ content: {
638
+ 'application/json': {
639
+ schema: {
640
+ type: 'object',
641
+ required: ['indices'],
642
+ properties: {
643
+ indices: {
644
+ type: 'array',
645
+ items: { type: 'integer', minimum: 1 },
646
+ minItems: 1,
647
+ description: 'Required 1-based creative slot numbers; Agent API does not auto-select by mode.',
648
+ },
649
+ regenerateMode: {
650
+ type: 'string',
651
+ enum: ['improve', 'fresh'],
652
+ default: 'fresh',
653
+ description: 'improve = UI regenerate; fresh = regenerate from scratch',
654
+ },
655
+ async: { type: 'boolean', default: true },
656
+ runValidator: { type: 'boolean', default: true },
657
+ uploadAfterOk: { type: 'boolean', description: 'Upload to Drive after validator OK' },
658
+ },
659
+ },
660
+ examples: {
661
+ explicitIndices: {
662
+ value: {
663
+ indices: [3, 10, 15, 16],
664
+ regenerateMode: 'fresh',
665
+ async: true,
666
+ },
667
+ },
668
+ },
669
+ },
670
+ },
671
+ },
672
+ responses: {
673
+ '200': { description: 'Regeneration completed for accepted slots' },
674
+ '202': { description: 'Async regeneration accepted; poll jobs/regenerateImages' },
675
+ '400': { description: 'No eligible slots or invalid payload' },
676
+ },
677
+ },
678
+ },
679
+ '/api/health': {
680
+ get: {
681
+ operationId: 'getHealth',
682
+ summary: 'Return Agent API liveness and session counts',
683
+ responses: { '200': { description: 'Health snapshot' } },
684
+ },
685
+ },
686
+ '/api/windows/{sessionId}/generated': {
687
+ get: {
688
+ operationId: 'getGenerated',
689
+ summary: 'Return generated titles, texts, images and catalog rows',
690
+ responses: { '200': { description: 'Generated session artifacts' } },
691
+ },
692
+ patch: {
693
+ operationId: 'patchGenerated',
694
+ summary: 'Partially edit generated titles, texts or image metadata without starting generation',
695
+ requestBody: {
696
+ required: true,
697
+ content: {
698
+ 'application/json': {
699
+ schema: { $ref: '#/components/schemas/PatchGeneratedRequest' },
700
+ examples: {
701
+ titleText: {
702
+ value: {
703
+ titles: [{ index: 1, title: 'New title' }],
704
+ texts: [{ index: 1, text: 'New text' }],
705
+ },
706
+ },
707
+ imageMetadata: {
708
+ value: {
709
+ images: [{ index: 1, failed: false, errorMessage: '' }],
710
+ },
711
+ },
712
+ },
713
+ },
714
+ },
715
+ },
716
+ responses: { '200': { description: 'Generated artifacts were updated' }, '400': { description: 'Invalid patch' } },
717
+ },
718
+ },
719
+ '/api/windows/{sessionId}/logs': {
720
+ get: {
721
+ operationId: 'getLogs',
722
+ summary: 'Return recent content, image and catalog replacement logs',
723
+ responses: { '200': { description: 'Session logs' } },
724
+ },
725
+ },
726
+ '/api/spec': {
727
+ get: {
728
+ operationId: 'getAgentApiSpec',
729
+ summary: 'Return the machine-readable Agent API description',
730
+ responses: {
731
+ '200': {
732
+ description: 'OpenAPI-like API description for automation agents',
733
+ },
734
+ },
735
+ },
736
+ },
737
+ },
738
+ components: {
739
+ schemas: {
740
+ AgentWorkSessionInfo: {
741
+ type: 'object',
742
+ required: ['id', 'title', 'description', 'active', 'url', 'endpoints'],
743
+ properties: {
744
+ id: {
745
+ type: 'string',
746
+ description: 'Stable workspace/session id to use in future agent operations.',
747
+ },
748
+ title: { type: 'string' },
749
+ description: {
750
+ type: 'string',
751
+ description: 'Short human-readable summary of what is open in this session.',
752
+ },
753
+ folderId: {
754
+ type: 'string',
755
+ description: 'Current Google Drive folder id, when selected.',
756
+ },
757
+ folderPath: {
758
+ type: 'string',
759
+ description: 'Two-level display path for the current Drive folder, for example OFFERS/OfferName.',
760
+ },
761
+ active: { type: 'boolean' },
762
+ url: {
763
+ type: 'string',
764
+ description: 'Full local URL for reading this session state.',
765
+ },
766
+ endpoints: {
767
+ type: 'object',
768
+ required: ['info', 'update', 'geoBlock', 'creativeSelections', 'capabilities', 'validation', 'generated', 'logs', 'jobs'],
769
+ properties: {
770
+ info: { type: 'string' },
771
+ update: { type: 'string' },
772
+ geoBlock: { type: 'string' },
773
+ creativeSelections: { type: 'string' },
774
+ capabilities: { type: 'string' },
775
+ validation: { type: 'string' },
776
+ generated: { type: 'string' },
777
+ logs: { type: 'string' },
778
+ jobs: { type: 'string' },
779
+ },
780
+ },
781
+ },
782
+ },
783
+ GeoBlockMarket: {
784
+ type: 'object',
785
+ properties: {
786
+ id: { type: 'string' },
787
+ geo: { type: 'string' },
788
+ priceWithCurrency: { type: 'string' },
789
+ link: { type: 'string' },
790
+ },
791
+ },
792
+ FillGeoBlockRequest: {
793
+ oneOf: [
794
+ { $ref: '#/components/schemas/GeoBlockMarket' },
795
+ {
796
+ type: 'object',
797
+ required: ['markets'],
798
+ properties: {
799
+ markets: {
800
+ type: 'array',
801
+ minItems: 1,
802
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
803
+ },
804
+ },
805
+ },
806
+ ],
807
+ },
808
+ PatchSessionInfoRequest: {
809
+ type: 'object',
810
+ description: 'Partial session update. Omitted fields are left unchanged.',
811
+ properties: {
812
+ generateProduct: { type: 'string' },
813
+ generateAdditionalInfo: { type: 'string' },
814
+ generateGeo: { type: 'string' },
815
+ generatePriceWithCurrency: { type: 'string' },
816
+ link: { type: 'string' },
817
+ generationMarkets: {
818
+ type: 'array',
819
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
820
+ },
821
+ markets: {
822
+ type: 'array',
823
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
824
+ },
825
+ geoBlock: {
826
+ type: 'array',
827
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
828
+ },
829
+ generation: {
830
+ type: 'object',
831
+ properties: {
832
+ product: { type: 'string' },
833
+ additionalInfo: { type: 'string' },
834
+ firstGeo: { type: 'string' },
835
+ firstPriceWithCurrency: { type: 'string' },
836
+ firstLink: { type: 'string' },
837
+ geoBlock: {
838
+ type: 'array',
839
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
840
+ },
841
+ },
842
+ },
843
+ catalogUrlSettings: { type: 'object' },
844
+ creativeSelections: { $ref: '#/components/schemas/SetCreativeSelectionsRequest' },
845
+ promptSettings: { type: 'object' },
846
+ drive: {
847
+ type: 'object',
848
+ description: 'Select campaign group, workspace folder and offer binding before generation.',
849
+ properties: {
850
+ groupName: { type: 'string' },
851
+ workspaceFolderId: { type: 'string' },
852
+ workspaceName: { type: 'string' },
853
+ offer: { type: 'string' },
854
+ },
855
+ },
856
+ },
857
+ },
858
+ SetupCampaignWorkspaceRequest: {
859
+ type: 'object',
860
+ required: ['groupName'],
861
+ properties: {
862
+ groupName: { type: 'string' },
863
+ workspaceFolderId: { type: 'string' },
864
+ workspaceName: { type: 'string' },
865
+ offer: { type: 'string' },
866
+ generation: { type: 'object' },
867
+ creativeSelections: { $ref: '#/components/schemas/SetCreativeSelectionsRequest' },
868
+ },
869
+ },
870
+ DriveCatalog: {
871
+ type: 'object',
872
+ description: 'Offers root, campaigns tree and current session Drive bindings.',
873
+ },
874
+ CreativeSelections: {
875
+ type: 'object',
876
+ required: ['textApproaches', 'imageApproaches'],
877
+ properties: {
878
+ textApproaches: {
879
+ type: 'object',
880
+ required: ['selectedIndices', 'selectedNumbers'],
881
+ properties: {
882
+ selectedIndices: {
883
+ type: 'array',
884
+ description: '0-based selected text approach indices.',
885
+ items: { type: 'number' },
886
+ },
887
+ selectedNumbers: {
888
+ type: 'array',
889
+ description: '1-based selected text approach numbers as shown in the UI.',
890
+ items: { type: 'number' },
891
+ },
892
+ },
893
+ },
894
+ imageApproaches: {
895
+ type: 'object',
896
+ required: ['counts', 'selectedIndices', 'selectedNumbers', 'maxPerApproach'],
897
+ properties: {
898
+ counts: {
899
+ type: 'array',
900
+ description: 'Image count per creative approach, 10 items, values from 0 to maxPerApproach.',
901
+ items: { type: 'number' },
902
+ },
903
+ selectedIndices: {
904
+ type: 'array',
905
+ description: '0-based image approach indices where count is greater than 0.',
906
+ items: { type: 'number' },
907
+ },
908
+ selectedNumbers: {
909
+ type: 'array',
910
+ description: '1-based image approach numbers where count is greater than 0.',
911
+ items: { type: 'number' },
912
+ },
913
+ maxPerApproach: { type: 'number' },
914
+ },
915
+ },
916
+ },
917
+ },
918
+ SetCreativeSelectionsRequest: {
919
+ type: 'object',
920
+ properties: {
921
+ selectedTextApproachIndices: {
922
+ type: 'array',
923
+ description: '0-based text approach indices. At least two unique approaches are required if provided.',
924
+ items: { type: 'number' },
925
+ },
926
+ selectedTextApproachNumbers: {
927
+ type: 'array',
928
+ description: '1-based text approach numbers as shown in the UI. At least two unique approaches are required if provided.',
929
+ items: { type: 'number' },
930
+ },
931
+ imageApproachCounts: {
932
+ type: 'array',
933
+ description: 'Image count per creative approach, 10 items, values from 0 to maxPerApproach.',
934
+ items: { type: 'number' },
935
+ },
936
+ selectedImageApproachIndices: {
937
+ type: 'array',
938
+ description: '0-based image approach indices. Each selected approach gets count=1.',
939
+ items: { type: 'number' },
940
+ },
941
+ selectedImageApproachNumbers: {
942
+ type: 'array',
943
+ description: '1-based image approach numbers as shown in the UI. Each selected approach gets count=1.',
944
+ items: { type: 'number' },
945
+ },
946
+ },
947
+ },
948
+ PatchGeneratedRequest: {
949
+ type: 'object',
950
+ description: 'Partial edit for generated artifacts. Omitted fields are unchanged.',
951
+ properties: {
952
+ titles: {
953
+ type: 'array',
954
+ items: {
955
+ type: 'object',
956
+ required: ['index'],
957
+ properties: {
958
+ index: { type: 'number', description: '1-based title row index.' },
959
+ title: { type: 'string' },
960
+ failed: { type: 'boolean' },
961
+ errorMessage: { type: 'string' },
962
+ },
963
+ },
964
+ },
965
+ texts: {
966
+ type: 'array',
967
+ items: {
968
+ type: 'object',
969
+ required: ['index'],
970
+ properties: {
971
+ index: { type: 'number', description: '1-based text row index.' },
972
+ text: { type: 'string' },
973
+ failed: { type: 'boolean' },
974
+ errorMessage: { type: 'string' },
975
+ },
976
+ },
977
+ },
978
+ images: {
979
+ type: 'array',
980
+ items: {
981
+ type: 'object',
982
+ required: ['index'],
983
+ properties: {
984
+ index: { type: 'number', description: '1-based image slot index.' },
985
+ imageUrl: { type: 'string' },
986
+ approach: { type: 'string' },
987
+ geo: { type: 'string' },
988
+ priceWithCurrency: { type: 'string' },
989
+ aspectRatio: { type: 'string' },
990
+ uploaded: { type: 'boolean' },
991
+ failed: { type: 'boolean' },
992
+ errorMessage: { type: 'string' },
993
+ checkStatus: { type: 'string', description: 'pending | checking | ok | needs_rebuild.' },
994
+ checkResult: { type: 'string' },
995
+ checkErrors: { type: 'array', items: { type: 'string' } },
996
+ checkFailed: {
997
+ type: 'boolean',
998
+ description: 'true = validator transport error (e.g. "Failed to fetch"), not a real rejection. The image is fine; just re-run the validator (check-images) instead of regenerating.',
999
+ },
1000
+ recommendedAction: {
1001
+ type: 'string',
1002
+ enum: ['recheck', 'rebuild'],
1003
+ description: 'Present only for problem slots. "recheck" → POST .../actions/check-images with this index (validator transport error). "rebuild" → POST .../actions/regenerate-images with this index (generation failed or validator rejected the image).',
1004
+ },
1005
+ },
1006
+ },
1007
+ },
1008
+ },
1009
+ },
1010
+ AgentSessionInfo: {
1011
+ type: 'object',
1012
+ description: 'Live renderer state for one Docs Combiner work session.',
1013
+ required: ['id', 'title', 'updatedAt', 'generation', 'snapshot'],
1014
+ properties: {
1015
+ id: { type: 'string' },
1016
+ title: { type: 'string' },
1017
+ updatedAt: { type: 'string', format: 'date-time' },
1018
+ drive: { type: 'object' },
1019
+ generation: {
1020
+ type: 'object',
1021
+ properties: {
1022
+ product: { type: 'string' },
1023
+ additionalInfo: { type: 'string' },
1024
+ geoBlock: {
1025
+ type: 'array',
1026
+ items: { $ref: '#/components/schemas/GeoBlockMarket' },
1027
+ },
1028
+ firstGeo: { type: 'string' },
1029
+ firstPriceWithCurrency: { type: 'string' },
1030
+ firstLink: { type: 'string' },
1031
+ brand: { type: 'string' },
1032
+ },
1033
+ },
1034
+ catalogUrlSettings: { type: 'object' },
1035
+ promptSettings: { type: 'object' },
1036
+ snapshot: {
1037
+ type: 'object',
1038
+ description: 'Serializable workspace snapshot used by the app autosave.',
1039
+ },
1040
+ },
1041
+ },
1042
+ },
1043
+ },
1044
+ };
1045
+ }