@kolbo/mcp 1.119.1 → 1.120.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/README.md +1 -0
- package/package.json +1 -1
- package/src/apps/bridge.js +25 -2
- package/src/apps/widgets/generation.js +16 -5
- package/src/apps/widgets/transcript.js +14 -6
- package/src/tools/generate.js +13 -6
- package/src/tools/presets.js +11 -8
package/README.md
CHANGED
|
@@ -321,6 +321,7 @@ Requires DaVinci Resolve Studio with the Kolbo plugin open (Workspace → Workfl
|
|
|
321
321
|
| `generate_character_sheet` | Generate a multi-angle character sheet from reference images (credits) → pass URL to create_visual_dna or update_visual_dna |
|
|
322
322
|
| `list_visual_dna_folders` / `create_visual_dna_folder` / `update_visual_dna_folder` / `delete_visual_dna_folder` / `move_visual_dna_to_folder` | Organize Visual DNA characters into user folders (create/rename/recolor/delete + move DNAs in/out) |
|
|
323
323
|
| `create_project` / `update_project` / `archive_project` / `unarchive_project` | Project lifecycle (create/rename/describe/archive; deletion stays in-app) |
|
|
324
|
+
| `transfer_visual_dna` / `list_visual_dna_transfers` / `respond_visual_dna_transfer` | Send Visual DNAs you own to another Kolbo account (they accept a copy with new ids; optionally move instead of copy) |
|
|
324
325
|
| `transfer_project` / `list_project_transfers` / `respond_project_transfer` | Hand a project you own to another Kolbo account (they accept; same ids; cast assets copied; payer history unchanged) |
|
|
325
326
|
| `list_skills` / `create_skill` / `update_skill` / `delete_skill` | Skills — reusable named personas for the chat tool; `description` is the system instruction |
|
|
326
327
|
| `list_agents` / `create_agent` / `update_agent` / `delete_agent` | Same four tools under their original names. Still supported and unchanged; new integrations should use the `*_skill` names |
|
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -55,10 +55,32 @@ const BRIDGE_JS = `
|
|
|
55
55
|
|
|
56
56
|
function post(msg) { window.parent.postMessage(msg, '*'); }
|
|
57
57
|
|
|
58
|
+
// Longer than the 180s stdio status wait plus transport overhead.
|
|
59
|
+
// This bounds tracking only; it never cancels or resubmits a paid job.
|
|
60
|
+
var REQUEST_TIMEOUT_MS = 240000;
|
|
61
|
+
function timeoutError() {
|
|
62
|
+
var error = new Error('The host did not answer. Tracking paused; the job may still be running.');
|
|
63
|
+
error.code = 'KOLBO_HOST_TIMEOUT';
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
function boundedCall(run) {
|
|
67
|
+
return new Promise(function (resolve, reject) {
|
|
68
|
+
var timer = setTimeout(function () { reject(timeoutError()); }, REQUEST_TIMEOUT_MS);
|
|
69
|
+
Promise.resolve().then(run).then(function (value) {
|
|
70
|
+
clearTimeout(timer); resolve(value);
|
|
71
|
+
}, function (error) { clearTimeout(timer); reject(error); });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
function request(method, params) {
|
|
59
76
|
return new Promise(function (resolve, reject) {
|
|
60
77
|
var id = nextId++;
|
|
61
|
-
|
|
78
|
+
var timer = setTimeout(function () {
|
|
79
|
+
delete pending[id];
|
|
80
|
+
queue = queue.filter(function (item) { return item.id !== id; });
|
|
81
|
+
reject(timeoutError());
|
|
82
|
+
}, REQUEST_TIMEOUT_MS);
|
|
83
|
+
pending[id] = { resolve: resolve, reject: reject, timer: timer };
|
|
62
84
|
var msg = { jsonrpc: '2.0', id: id, method: method, params: params || {} };
|
|
63
85
|
if (initialized || method === 'ui/initialize') post(msg);
|
|
64
86
|
else queue.push(msg);
|
|
@@ -78,6 +100,7 @@ const BRIDGE_JS = `
|
|
|
78
100
|
var p = pending[m.id];
|
|
79
101
|
if (!p) return;
|
|
80
102
|
delete pending[m.id];
|
|
103
|
+
clearTimeout(p.timer);
|
|
81
104
|
if (m.error) p.reject(new Error(m.error.message || 'host error'));
|
|
82
105
|
else p.resolve(m.result);
|
|
83
106
|
return;
|
|
@@ -282,7 +305,7 @@ const BRIDGE_JS = `
|
|
|
282
305
|
var oaCall = function (method) { return typeof oa[method] === 'function'; };
|
|
283
306
|
window.kolbo.callTool = function (name, args) {
|
|
284
307
|
if (!oaCall('callTool')) return Promise.reject(new Error('host cannot call tools'));
|
|
285
|
-
return
|
|
308
|
+
return boundedCall(function () { return oa.callTool(name, args || {}); }).then(function (r) {
|
|
286
309
|
// The SDK resolves with the CallToolResult (content + structuredContent);
|
|
287
310
|
// some builds unwrap to the structured payload — normalise either.
|
|
288
311
|
if (r && (r.structuredContent || r.content)) return r;
|
|
@@ -802,7 +802,10 @@ function schedulePoll(sc) {
|
|
|
802
802
|
// (~45s over the remote connector — see WAIT_WINDOW_MS in tools/generate.js).
|
|
803
803
|
// This short pause only separates successive wait windows — the FIRST call
|
|
804
804
|
// goes out immediately, so a card revealed by scrolling resolves at once.
|
|
805
|
-
|
|
805
|
+
// After failed calls, back off exponentially (capped at 30s): a host that
|
|
806
|
+
// rejects tools/call for a while (busy, rate-limited, reconnecting) used to
|
|
807
|
+
// burn all MAX_POLL_ERRORS retries in ~45s and strand finished generations.
|
|
808
|
+
var delay = pollStart ? Math.min(1500 * Math.pow(2, pollErrors), 30000) : 0;
|
|
806
809
|
if (!pollStart) pollStart = Date.now();
|
|
807
810
|
clearTimeout(pollTimer);
|
|
808
811
|
pollTimer = setTimeout(function () { poll(sc); }, delay);
|
|
@@ -813,6 +816,9 @@ function poll(sc) {
|
|
|
813
816
|
return renderTrackingIssue('This is still running longer than the tracking window. Do not retry it — any completed result will appear in your Kolbo library.');
|
|
814
817
|
}
|
|
815
818
|
var args = sc.status_args || { generation_id: sc.generation_id, wait: true };
|
|
819
|
+
// Host-bridged calls may not reset their timeout on progress: keep each
|
|
820
|
+
// wait window inside the common 60s request timeout.
|
|
821
|
+
if (args.wait) args = Object.assign({}, args, { wait_seconds: 45 });
|
|
816
822
|
pollInFlight = true;
|
|
817
823
|
window.kolbo.callTool(sc.poll_tool || 'get_generation_status', args).then(function (res) {
|
|
818
824
|
pollInFlight = false;
|
|
@@ -870,7 +876,7 @@ function poll(sc) {
|
|
|
870
876
|
pollErrors = 0; // a valid in-progress response resets the failure streak
|
|
871
877
|
schedulePoll(sc);
|
|
872
878
|
}
|
|
873
|
-
}).catch(function () {
|
|
879
|
+
}).catch(function (error) {
|
|
874
880
|
pollInFlight = false;
|
|
875
881
|
if (cancelRequested || (state && (state.phase === 'completed' || state.phase === 'failed'))) return;
|
|
876
882
|
if (++pollErrors >= MAX_POLL_ERRORS) return renderTrackingIssue('Tracking paused after repeated connection errors. The generation may still be running.');
|
|
@@ -1270,13 +1276,18 @@ function renderError(msg) {
|
|
|
1270
1276
|
|
|
1271
1277
|
function renderTrackingIssue(msg) {
|
|
1272
1278
|
clearTimeout(pollTimer);
|
|
1273
|
-
setPhaseChip('
|
|
1279
|
+
setPhaseChip('Tracking paused', false);
|
|
1274
1280
|
el('stage').innerHTML = '<div class="k-error">' + ICONS.clock + ' ' + esc(msg) + '</div>';
|
|
1275
1281
|
el('actions').innerHTML = '<button class="k-btn primary" id="status-btn">' + ICONS.clock + ' Check status</button>' +
|
|
1276
1282
|
'<button class="k-btn ghost" id="tracking-open">Open in Kolbo ' + ICONS.open + '</button>';
|
|
1283
|
+
// Resume tracking in place: the card re-checks the SAME generation ids
|
|
1284
|
+
// (never a resubmit). A chat message alone left the card red even after
|
|
1285
|
+
// the agent learned the job had finished.
|
|
1277
1286
|
el('status-btn').onclick = function () {
|
|
1278
|
-
|
|
1279
|
-
|
|
1287
|
+
if (!state) return;
|
|
1288
|
+
pollErrors = 0; pollStart = 0;
|
|
1289
|
+
setPhaseChip('Checking', true);
|
|
1290
|
+
schedulePoll(state);
|
|
1280
1291
|
};
|
|
1281
1292
|
el('tracking-open').onclick = function () {
|
|
1282
1293
|
window.kolbo.openLink(kolboUrl(state));
|
|
@@ -40,6 +40,7 @@ var state = null, pollTimer = null, originArgs = {};
|
|
|
40
40
|
|
|
41
41
|
function boot(sc) {
|
|
42
42
|
if (!sc) return;
|
|
43
|
+
clearTimeout(pollTimer);
|
|
43
44
|
state = sc;
|
|
44
45
|
el('credits').textContent = sc.credits_used != null ? fmtCredits(sc.credits_used) : '';
|
|
45
46
|
if (sc.phase === 'generating') {
|
|
@@ -93,6 +94,8 @@ function poll() {
|
|
|
93
94
|
return trackingIssue('This transcription is still running longer than the tracking window. Do not retry it — any completed result will appear in your Kolbo library.');
|
|
94
95
|
}
|
|
95
96
|
var args = state.status_args || { generation_id: state.generation_id, wait: true };
|
|
97
|
+
// Keep each host-bridged wait inside the common 60s request timeout.
|
|
98
|
+
if (args.wait) args = Object.assign({}, args, { wait_seconds: 45 });
|
|
96
99
|
window.kolbo.callTool(state.poll_tool || 'get_generation_status', args)
|
|
97
100
|
.then(function (res) {
|
|
98
101
|
var st = structured(res) || {};
|
|
@@ -107,7 +110,8 @@ function poll() {
|
|
|
107
110
|
} else if (s === 'failed' || s === 'cancelled') {
|
|
108
111
|
boot(Object.assign({}, state, { phase: 'failed', error: st.error }));
|
|
109
112
|
} else { pollErrors = 0; pollTimer = setTimeout(poll, pollDelay()); }
|
|
110
|
-
}).catch(function () {
|
|
113
|
+
}).catch(function (error) {
|
|
114
|
+
if (state && state.phase !== 'generating') return;
|
|
111
115
|
if (++pollErrors >= MAX_POLL_ERRORS) return trackingIssue('Tracking paused after repeated connection errors. The transcription may still be running.');
|
|
112
116
|
pollTimer = setTimeout(poll, pollDelay());
|
|
113
117
|
});
|
|
@@ -115,20 +119,24 @@ function poll() {
|
|
|
115
119
|
|
|
116
120
|
function pollDelay() {
|
|
117
121
|
// The call itself long-waits server-side; only briefly back off between
|
|
118
|
-
// successive wait windows
|
|
119
|
-
|
|
122
|
+
// successive wait windows; after failed calls, back off exponentially
|
|
123
|
+
// (capped at 30s) so a briefly unavailable host can't exhaust the retries.
|
|
124
|
+
return Math.min(1500 * Math.pow(2, pollErrors), 30000);
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
function trackingIssue(msg) {
|
|
123
128
|
clearTimeout(pollTimer);
|
|
124
129
|
el('phase-chip').style.display = '';
|
|
125
|
-
el('phase-chip').textContent = '
|
|
130
|
+
el('phase-chip').textContent = 'Tracking paused';
|
|
126
131
|
el('stage').classList.remove('k-empty');
|
|
127
132
|
el('stage').innerHTML = '<div class="k-error">' + ICONS.clock + ' ' + esc(msg) + '</div>';
|
|
128
133
|
el('actions').innerHTML = '<button class="k-btn primary" id="transcript-status">' + ICONS.clock + ' Check status</button>';
|
|
134
|
+
// Resume tracking in place (same generation id, never a resubmit).
|
|
129
135
|
el('transcript-status').onclick = function () {
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
if (!state) return;
|
|
137
|
+
pollErrors = 0;
|
|
138
|
+
el('actions').innerHTML = '';
|
|
139
|
+
boot(Object.assign({}, state, { phase: 'generating' }));
|
|
132
140
|
};
|
|
133
141
|
window.kolbo.notifySize();
|
|
134
142
|
}
|
package/src/tools/generate.js
CHANGED
|
@@ -604,16 +604,17 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
604
604
|
'Check the status of a Creative Director batch (from generate_creative_director) by its generation_id. Returns overall state ("processing" until EVERY scene is terminal, then "completed"/"failed") plus each scene\'s number, title, per-scene status, and image_urls/video_urls. Set wait=true to block until the batch is terminal or the wait window closes, whichever comes first — a batch longer than one window returns state="processing" and you simply call again with wait=true. Prefer this over the generic get_generation_status for Creative Director ids — the generic tool now returns the same scene data (it delegates here), but this one is the direct route.',
|
|
605
605
|
{
|
|
606
606
|
generation_id: z.string().describe('The Creative Director generation_id returned by generate_creative_director.'),
|
|
607
|
-
wait: z.boolean().optional().describe(`If true, block until the batch is terminal, for at most ~${WAIT_WINDOW_S}s per call. A batch that outlives one window comes back state="processing" (not an error) — call again with wait=true until it is terminal. Always prefer this over polling with wait=false.`)
|
|
607
|
+
wait: z.boolean().optional().describe(`If true, block until the batch is terminal, for at most ~${WAIT_WINDOW_S}s per call. A batch that outlives one window comes back state="processing" (not an error) — call again with wait=true until it is terminal. Always prefer this over polling with wait=false.`),
|
|
608
|
+
wait_seconds: z.number().min(1).max(180).optional().describe('Optional cap on one wait=true window, in seconds (default and maximum: the transport window). Interactive cards pass 45 so each call fits a host 60s request timeout.')
|
|
608
609
|
},
|
|
609
|
-
async ({ generation_id, wait }) => {
|
|
610
|
+
async ({ generation_id, wait, wait_seconds }) => {
|
|
610
611
|
const statusUrl = `/v1/generate/creative-director/${encodeURIComponent(generation_id)}/status`;
|
|
611
612
|
let status;
|
|
612
613
|
if (wait) {
|
|
613
614
|
try {
|
|
614
615
|
status = await pollUntilDone(client, generation_id, {
|
|
615
616
|
interval: 15000,
|
|
616
|
-
timeout: WAIT_WINDOW_MS,
|
|
617
|
+
timeout: wait_seconds ? Math.min(wait_seconds * 1000, WAIT_WINDOW_MS) : WAIT_WINDOW_MS,
|
|
617
618
|
statusUrl
|
|
618
619
|
});
|
|
619
620
|
} catch (err) {
|
|
@@ -1348,14 +1349,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1348
1349
|
{
|
|
1349
1350
|
generation_id: z.string().optional().describe('A single generation ID to check'),
|
|
1350
1351
|
generation_ids: z.array(z.string()).max(MAX_STATUS_IDS).optional().describe(`Multiple generation IDs to check in ONE call (max ${MAX_STATUS_IDS}). Returns { all_done, pending, generations[] } — always prefer this over checking IDs one by one.`),
|
|
1351
|
-
wait: z.boolean().optional().describe(`If true, block until every generation reaches a terminal state (completed/failed), for at most ~${WAIT_WINDOW_S}s per call, then return whatever state they are in. Anything still processing is reported, not errored — re-issue with wait=true and only the still-pending ids. This is always better than polling with wait=false.`)
|
|
1352
|
+
wait: z.boolean().optional().describe(`If true, block until every generation reaches a terminal state (completed/failed), for at most ~${WAIT_WINDOW_S}s per call, then return whatever state they are in. Anything still processing is reported, not errored — re-issue with wait=true and only the still-pending ids. This is always better than polling with wait=false.`),
|
|
1353
|
+
wait_seconds: z.number().min(1).max(180).optional().describe('Optional cap on one wait=true window, in seconds (default and maximum: the transport window). Interactive cards pass 45 so each call fits a host 60s request timeout.')
|
|
1352
1354
|
},
|
|
1353
|
-
async ({ generation_id, generation_ids, wait }) => {
|
|
1355
|
+
async ({ generation_id, generation_ids, wait, wait_seconds }) => {
|
|
1354
1356
|
const ids = (generation_ids && generation_ids.length > 0)
|
|
1355
1357
|
? [...new Set(generation_ids)]
|
|
1356
1358
|
: (generation_id ? [generation_id] : []);
|
|
1357
1359
|
if (ids.length === 0) throw new Error('Provide generation_id or generation_ids');
|
|
1358
1360
|
if (ids.length > MAX_STATUS_IDS) throw new Error(`At most ${MAX_STATUS_IDS} generation IDs can be checked at once.`);
|
|
1361
|
+
// Widget polls arrive through the host's iframe bridge, which (unlike the
|
|
1362
|
+
// model's own calls) need not reset its request timeout on our progress
|
|
1363
|
+
// ticks — a 180s stdio window there timed out every poll, stranding cards
|
|
1364
|
+
// on Generating and then "Tracking paused". Widgets pass wait_seconds.
|
|
1365
|
+
const windowMs = wait_seconds ? Math.min(wait_seconds * 1000, WAIT_WINDOW_MS) : WAIT_WINDOW_MS;
|
|
1359
1366
|
|
|
1360
1367
|
// One status check (or blocking poll) per id. Never let one bad id
|
|
1361
1368
|
// reject the whole batch — surface it as a failed entry instead.
|
|
@@ -1366,7 +1373,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1366
1373
|
// completion responsive without multiplying backend traffic for
|
|
1367
1374
|
// every card left open in a host conversation. The window itself is
|
|
1368
1375
|
// bounded by the transport — see WAIT_WINDOW_MS above.
|
|
1369
|
-
const result = await pollUntilDone(client, id, { interval: 15000, timeout:
|
|
1376
|
+
const result = await pollUntilDone(client, id, { interval: 15000, timeout: windowMs });
|
|
1370
1377
|
return { generation_id: id, ...result };
|
|
1371
1378
|
}
|
|
1372
1379
|
const result = await client.get(`/v1/generate/${encodeURIComponent(id)}/status`);
|
package/src/tools/presets.js
CHANGED
|
@@ -44,16 +44,17 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
44
44
|
: 'Pass search next time if you already know the name. Pass the chosen exact id as preset_id.',
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
if (lookup) return { content: [{ type: 'text', text }] };
|
|
48
|
-
|
|
49
47
|
// TOOL_WIDGETS declares mediaGrid for this tool, so hosts that mount from
|
|
50
|
-
// the declaration (Claude desktop)
|
|
51
|
-
//
|
|
52
|
-
// the
|
|
48
|
+
// the declaration (Claude desktop) prepare a grid on EVERY call — the
|
|
49
|
+
// search lookup included. A text-only lookup left that grid guessing from
|
|
50
|
+
// the compact JSON: thumbnail-less file tiles under the UNFILTERED total.
|
|
51
|
+
// structuredContent also shadows the text on those hosts, so it carries
|
|
52
|
+
// the ids, total and note the agent needs.
|
|
53
|
+
const total = search && !result.search ? presets.length : (result.count || presets.length);
|
|
53
54
|
return uiResult(UI.mediaGrid, text, {
|
|
54
55
|
widget: 'mediaGrid',
|
|
55
56
|
title: 'Presets' + (type ? ' — ' + type : ''),
|
|
56
|
-
items: presets.slice(0, 300).map(p => ({
|
|
57
|
+
items: presets.slice(0, lookup ? 8 : 300).map(p => ({
|
|
57
58
|
id: p.id,
|
|
58
59
|
title: p.name,
|
|
59
60
|
subtitle: p.category,
|
|
@@ -63,8 +64,10 @@ function registerPresetTools(server, client, options = {}) {
|
|
|
63
64
|
url: p.thumbnail_url || p.thumbnail,
|
|
64
65
|
use_hint: 'Use preset "{TITLE}" (preset_id: {ID}) for my next generation — ask me for the prompt.'
|
|
65
66
|
})),
|
|
66
|
-
total
|
|
67
|
-
has_more: presets.length > 300
|
|
67
|
+
total,
|
|
68
|
+
has_more: presets.length > (lookup ? 8 : 300),
|
|
69
|
+
...(lookup ? { _lookup: true, note: 'Pick the closest id and pass it as preset_id.' } : {}),
|
|
70
|
+
...(result.warning ? { warning: result.warning } : {}),
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
73
|
);
|