@kolbo/mcp 1.87.5 → 1.87.6
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/package.json +1 -1
- package/src/apps/index.js +7 -1
- package/src/apps/widgets/generation.js +98 -7
- package/src/tools/generate.js +14 -6
- package/src/tools/presets.js +8 -4
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -71,7 +71,13 @@ const WIDGET_CSP = {
|
|
|
71
71
|
// Public hosts owned by Kolbo.
|
|
72
72
|
'https://api.kolbo.ai',
|
|
73
73
|
'https://app.kolbo.ai',
|
|
74
|
-
'https://cdn.kolbo.ai',
|
|
74
|
+
'https://cdn.kolbo.ai',
|
|
75
|
+
// Preset thumbnails and library media live on the media-* hosts (prod
|
|
76
|
+
// presets still reference media-dev); without these every preset tile
|
|
77
|
+
// rendered as a black box. All three are on kolbo-api's download allowlist.
|
|
78
|
+
'https://media.kolbo.ai',
|
|
79
|
+
'https://media-staging.kolbo.ai',
|
|
80
|
+
'https://media-dev.kolbo.ai',
|
|
75
81
|
...KOLBO_MEDIA_DOMAINS,
|
|
76
82
|
'https://kolbo-general-media.fra1.digitaloceanspaces.com',
|
|
77
83
|
'https://kolbo-general-media.fra1.cdn.digitaloceanspaces.com',
|
|
@@ -195,6 +195,7 @@ function isListPayload(sc, toolName) {
|
|
|
195
195
|
function boot(sc) {
|
|
196
196
|
if (!sc) return;
|
|
197
197
|
if (isListPayload(sc, sc.tool)) return renderList(sc);
|
|
198
|
+
sc = liveFromStatus(sc);
|
|
198
199
|
state = sc;
|
|
199
200
|
el('tool-title').textContent = TOOL_TITLES[sc.tool] || 'Generation';
|
|
200
201
|
setPrompt(sc.prompt ? promptHTML(sc.prompt) : '', sc.prompt);
|
|
@@ -486,6 +487,51 @@ function capAt(sc, i) {
|
|
|
486
487
|
return sc._caps[i] || sc.prompts[i];
|
|
487
488
|
}
|
|
488
489
|
|
|
490
|
+
// A completed TOOL CALL is not a completed GENERATION. When a generate_* call
|
|
491
|
+
// outlives its poll window on a text host (Claude desktop over stdio), the
|
|
492
|
+
// server answers with a kind:'status' grid whose items are still processing,
|
|
493
|
+
// and that used to render as a dead card of "processing" badges that never
|
|
494
|
+
// polled again: the user watched skeletons that would never fill while the
|
|
495
|
+
// images landed in the library. Turn any such payload back into a LIVE card,
|
|
496
|
+
// the same generating/polling path a fresh submit takes, cells filled per id.
|
|
497
|
+
function isTerminal(s) { return s === 'completed' || s === 'failed' || s === 'cancelled'; }
|
|
498
|
+
function liveFromStatus(sc) {
|
|
499
|
+
if (!sc || sc.phase !== 'completed' || !Array.isArray(sc.items) || !sc.items.length) return sc;
|
|
500
|
+
var ids = sc.items.map(function (it) { return it && it.id; });
|
|
501
|
+
if (ids.some(function (id) { return !id; })) return sc;
|
|
502
|
+
if (!sc.items.some(function (it) { return !isTerminal(it.state); })) return sc;
|
|
503
|
+
var kind = sc.kind === 'status' || !sc.kind ? kindFromTool(sc.tool, {}) : sc.kind;
|
|
504
|
+
var live = Object.assign({}, sc, {
|
|
505
|
+
phase: 'generating', kind: kind, items: undefined,
|
|
506
|
+
count: ids.length, poll_tool: 'get_generation_status', generation_id: ids[0]
|
|
507
|
+
});
|
|
508
|
+
if (ids.length > 1) {
|
|
509
|
+
live.generation_ids = ids;
|
|
510
|
+
live.prompts = sc.items.map(function (it) { return it.title || ''; });
|
|
511
|
+
live.status_args = { generation_ids: ids, wait: true };
|
|
512
|
+
} else {
|
|
513
|
+
live.status_args = { generation_id: ids[0], wait: true };
|
|
514
|
+
}
|
|
515
|
+
return live;
|
|
516
|
+
}
|
|
517
|
+
// Multi-id status responses carry their per-generation results as items[] in
|
|
518
|
+
// structuredContent (the shape the status grid renders) and as generations[]
|
|
519
|
+
// only in the text, and structured() reads structuredContent first, so a live
|
|
520
|
+
// batch card never saw generations[] on a host that ships structuredContent.
|
|
521
|
+
function itemsToGens(items) {
|
|
522
|
+
if (!Array.isArray(items)) return null;
|
|
523
|
+
return items.map(function (it) {
|
|
524
|
+
return {
|
|
525
|
+
generation_id: it.id, state: it.state, credits_used: it.credits_used,
|
|
526
|
+
result: {
|
|
527
|
+
urls: Array.isArray(it.urls) ? it.urls : (it.url ? [it.url] : []),
|
|
528
|
+
model: it.model, model_name: it.model_name, model_icon: it.model_icon,
|
|
529
|
+
reference_images: it.reference_images
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
489
535
|
function renderGenerating(sc) {
|
|
490
536
|
setPhaseChip('Generating', true);
|
|
491
537
|
var n = Math.min(sc.count || 1, isBatch(sc) ? 8 : 4);
|
|
@@ -670,9 +716,14 @@ function poll(sc) {
|
|
|
670
716
|
if (++pollErrors >= MAX_POLL_ERRORS) return renderTrackingIssue(st.error || 'Tracking paused. The generation may still be running.');
|
|
671
717
|
return schedulePoll(sc);
|
|
672
718
|
}
|
|
673
|
-
// Batch (prompts[] fan-out): multi-id status shape { all_done, generations[] }
|
|
674
|
-
|
|
675
|
-
|
|
719
|
+
// Batch (prompts[] fan-out): multi-id status shape { all_done, generations[] },
|
|
720
|
+
// or the same data as items[] when it came via structuredContent.
|
|
721
|
+
var gens = Array.isArray(st.generations) ? st.generations : itemsToGens(st.items);
|
|
722
|
+
if (isBatch(sc) && gens) {
|
|
723
|
+
return handleBatchStatus(sc, {
|
|
724
|
+
all_done: st.all_done != null ? st.all_done : gens.every(function (g) { return isTerminal(g.state); }),
|
|
725
|
+
generations: gens
|
|
726
|
+
});
|
|
676
727
|
}
|
|
677
728
|
if (stateName === 'completed') {
|
|
678
729
|
pollErrors = 0;
|
|
@@ -724,8 +775,13 @@ function poll(sc) {
|
|
|
724
775
|
function handleBatchStatus(sc, st) {
|
|
725
776
|
pollErrors = 0;
|
|
726
777
|
var gens = st.generations || [];
|
|
778
|
+
// Cells are laid out in submit order; match results by id, not position.
|
|
779
|
+
var cellIndex = function (g, i) {
|
|
780
|
+
var idx = sc.generation_ids ? sc.generation_ids.indexOf(g.generation_id) : -1;
|
|
781
|
+
return idx < 0 ? i : idx;
|
|
782
|
+
};
|
|
727
783
|
gens.forEach(function (g, i) {
|
|
728
|
-
if (g.state === 'completed') fillBatchCell(sc, i, g);
|
|
784
|
+
if (g.state === 'completed') fillBatchCell(sc, cellIndex(g, i), g);
|
|
729
785
|
});
|
|
730
786
|
if (!st.all_done) return schedulePoll(sc);
|
|
731
787
|
|
|
@@ -753,8 +809,8 @@ function handleBatchStatus(sc, st) {
|
|
|
753
809
|
resolved.reference_images = r.reference_images;
|
|
754
810
|
}
|
|
755
811
|
scenes.push({
|
|
756
|
-
scene_number: i + 1,
|
|
757
|
-
title: capAt(sc, i),
|
|
812
|
+
scene_number: cellIndex(g, i) + 1,
|
|
813
|
+
title: capAt(sc, cellIndex(g, i)),
|
|
758
814
|
image_urls: sc.kind === 'video' ? [] : urls,
|
|
759
815
|
video_urls: sc.kind === 'video' ? urls : []
|
|
760
816
|
});
|
|
@@ -1305,6 +1361,41 @@ function kindFromTool(tool, sc) {
|
|
|
1305
1361
|
|
|
1306
1362
|
// Recover a successful legacy/text result when a host mounted the iframe from
|
|
1307
1363
|
// declaration metadata but the server did not recognize its Apps capability.
|
|
1364
|
+
// Every generate_* tool on a text host answers a poll-window timeout with the
|
|
1365
|
+
// same plain shape from pollOrTimedOut(): { state:'processing', generation_id,
|
|
1366
|
+
// _timed_out }. No phase, no widget, no urls, so nothing above recognised it
|
|
1367
|
+
// and the card stayed on its pre-render skeleton forever while the generation
|
|
1368
|
+
// finished in the library. Rebuild the live card from the tool INPUT (same
|
|
1369
|
+
// source completedFromPlain uses) and let it poll to completion.
|
|
1370
|
+
function liveFromTimedOut(sc) {
|
|
1371
|
+
if (!sc || !sc.generation_id || isTerminal(sc.state) || Array.isArray(sc.urls)) return null;
|
|
1372
|
+
var ids = Array.isArray(sc.generation_ids) && sc.generation_ids.length > 1 ? sc.generation_ids : null;
|
|
1373
|
+
var live = {
|
|
1374
|
+
widget: 'generation',
|
|
1375
|
+
phase: 'generating',
|
|
1376
|
+
tool: originTool,
|
|
1377
|
+
kind: kindFromTool(originTool, sc),
|
|
1378
|
+
prompt: originArgs.prompt || originArgs.text || '',
|
|
1379
|
+
model: sc.model || originArgs.model,
|
|
1380
|
+
settings: {
|
|
1381
|
+
duration: originArgs.duration,
|
|
1382
|
+
resolution: originArgs.resolution,
|
|
1383
|
+
aspect_ratio: originArgs.aspect_ratio,
|
|
1384
|
+
quality: originArgs.quality,
|
|
1385
|
+
visual_dna_ids: originArgs.visual_dna_ids,
|
|
1386
|
+
moodboard_id: originArgs.moodboard_id
|
|
1387
|
+
},
|
|
1388
|
+
count: ids ? ids.length : (originArgs.num_images || 1),
|
|
1389
|
+
generation_id: sc.generation_id,
|
|
1390
|
+
poll_tool: 'get_generation_status',
|
|
1391
|
+
status_args: ids ? { generation_ids: ids, wait: true } : { generation_id: sc.generation_id, wait: true },
|
|
1392
|
+
session_id: sc.session_id || originArgs.session_id,
|
|
1393
|
+
project_id: sc.project_id || originArgs.project_id
|
|
1394
|
+
};
|
|
1395
|
+
if (ids) { live.generation_ids = ids; live.prompts = originArgs.prompts || []; }
|
|
1396
|
+
return live;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1308
1399
|
function completedFromPlain(sc) {
|
|
1309
1400
|
if (!sc || (!Array.isArray(sc.urls) && !Array.isArray(sc.scenes))) return null;
|
|
1310
1401
|
return Object.assign({}, sc, {
|
|
@@ -1333,7 +1424,7 @@ window.kolbo.onToolResult(function (result) {
|
|
|
1333
1424
|
var list = listPayload(sc);
|
|
1334
1425
|
if (list) return renderList(list);
|
|
1335
1426
|
if (sc && (sc.phase || sc.widget)) return boot(sc);
|
|
1336
|
-
var recovered = completedFromPlain(sc);
|
|
1427
|
+
var recovered = completedFromPlain(sc) || liveFromTimedOut(sc);
|
|
1337
1428
|
if (recovered) return boot(recovered);
|
|
1338
1429
|
// Tool errored (or returned plain text): show it instead of a dead blank card.
|
|
1339
1430
|
var txt = '';
|
package/src/tools/generate.js
CHANGED
|
@@ -88,7 +88,7 @@ async function submitBatch(rawItems, submitOne) {
|
|
|
88
88
|
// multi-id branch, same fix: always ship structuredContent via the shared
|
|
89
89
|
// kind:'status' grid, which already renders any mix of completed/processing
|
|
90
90
|
// items correctly.
|
|
91
|
-
async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
91
|
+
async function pollBatch(client, batch, { interval, timeout }, toolName, submittedModel) {
|
|
92
92
|
const polls = await Promise.all(batch.ids.map((id) => pollOrTimedOut(client, id, { interval, timeout })));
|
|
93
93
|
const generations = polls.map((p, i) => p.timedOut
|
|
94
94
|
? { prompt: batch.ok[i].prompt, generation_id: batch.ids[i], status: 'processing', note: 'Still running — call get_generation_status with wait=true to collect it.' }
|
|
@@ -103,7 +103,7 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
|
103
103
|
const modelsRan = [...new Set(polls.filter((p) => !p.timedOut).map((p) => p.result.result && p.result.result.model).filter(Boolean))];
|
|
104
104
|
return uiCompleted({
|
|
105
105
|
tool: toolName, kind: 'status', client,
|
|
106
|
-
model: modelsRan.length === 1 ? modelsRan[0] : 'Generations',
|
|
106
|
+
model: modelsRan.length === 1 ? modelsRan[0] : (submittedModel || 'Generations'),
|
|
107
107
|
gen: { generation_id: batch.ids[0], session_id: batch.ok[0].gen.session_id },
|
|
108
108
|
settings: {},
|
|
109
109
|
items: generations.map(g => ({
|
|
@@ -111,6 +111,8 @@ async function pollBatch(client, batch, { interval, timeout }, toolName) {
|
|
|
111
111
|
state: g.status,
|
|
112
112
|
title: g.prompt,
|
|
113
113
|
url: Array.isArray(g.urls) ? g.urls[0] : undefined,
|
|
114
|
+
urls: Array.isArray(g.urls) ? g.urls : undefined,
|
|
115
|
+
credits_used: g.credits_used,
|
|
114
116
|
})),
|
|
115
117
|
}, text);
|
|
116
118
|
}
|
|
@@ -292,7 +294,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
292
294
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
293
295
|
reference_images
|
|
294
296
|
});
|
|
295
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image');
|
|
297
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image', model);
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
const gen = await client.post('/v1/generate/image', { ...shared, prompt, num_images });
|
|
@@ -381,7 +383,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
381
383
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
382
384
|
reference_images: [...(source_images || []), ...(reference_images || [])]
|
|
383
385
|
});
|
|
384
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit');
|
|
386
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 3) * 1000, timeout: 150000 }, 'generate_image_edit', model);
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
const gen = await client.post('/v1/generate/image-edit', { ...shared, prompt, num_images });
|
|
@@ -634,7 +636,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
634
636
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
635
637
|
reference_images
|
|
636
638
|
});
|
|
637
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 150000 }, 'generate_video');
|
|
639
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 150000 }, 'generate_video', model);
|
|
638
640
|
}
|
|
639
641
|
|
|
640
642
|
const gen = await client.post('/v1/generate/video', { ...shared, prompt });
|
|
@@ -724,7 +726,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
724
726
|
status_args: { generation_ids: batch.ids, wait: true },
|
|
725
727
|
reference_images: items.map((item) => item.image_url)
|
|
726
728
|
});
|
|
727
|
-
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video_from_image');
|
|
729
|
+
return pollBatch(client, batch, { interval: (batch.ok[0].gen.poll_interval_hint || 8) * 1000, timeout: 900000 }, 'generate_video_from_image', model);
|
|
728
730
|
}
|
|
729
731
|
|
|
730
732
|
const gen = await client.post('/v1/generate/video/from-image', { ...shared, image_url, prompt });
|
|
@@ -1249,6 +1251,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1249
1251
|
state: r.state,
|
|
1250
1252
|
title: res.prompt_used || res.prompt || undefined,
|
|
1251
1253
|
url: Array.isArray(res.urls) ? res.urls[0] : undefined,
|
|
1254
|
+
// A live batch card reads these from structuredContent (its only
|
|
1255
|
+
// view of this response) to fill each cell and repaint its chips.
|
|
1256
|
+
urls: Array.isArray(res.urls) ? res.urls : undefined,
|
|
1257
|
+
credits_used: creditFields(r).credits_used,
|
|
1258
|
+
model: res.model, model_name: res.model_name, model_icon: res.model_icon,
|
|
1259
|
+
reference_images: res.reference_images,
|
|
1252
1260
|
};
|
|
1253
1261
|
}),
|
|
1254
1262
|
}, text, extraContent);
|
package/src/tools/presets.js
CHANGED
|
@@ -46,10 +46,14 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
46
46
|
|
|
47
47
|
if (lookup) return { content: [{ type: 'text', text }] };
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// TOOL_WIDGETS declares mediaGrid for this tool, so hosts that mount from
|
|
50
|
+
// the declaration (Claude desktop) rendered a media grid fed a list-shaped
|
|
51
|
+
// 8-item page with no page_tool: a "Load more" that could never load. Ship
|
|
52
|
+
// the whole catalog (a few hundred small rows) to the grid it actually mounts.
|
|
53
|
+
return uiResult(UI.mediaGrid, text, {
|
|
54
|
+
widget: 'mediaGrid',
|
|
51
55
|
title: 'Presets' + (type ? ' — ' + type : ''),
|
|
52
|
-
items: presets.slice(0,
|
|
56
|
+
items: presets.slice(0, 300).map(p => ({
|
|
53
57
|
id: p.id,
|
|
54
58
|
title: p.name,
|
|
55
59
|
subtitle: p.category,
|
|
@@ -60,7 +64,7 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
60
64
|
use_hint: 'Use preset "{TITLE}" (preset_id: {ID}) for my next generation — ask me for the prompt.'
|
|
61
65
|
})),
|
|
62
66
|
total: result.count || presets.length,
|
|
63
|
-
has_more: presets.length >
|
|
67
|
+
has_more: presets.length > 300
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
);
|