@wix/mcp 1.0.94 → 1.0.96
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 +2 -2
- package/build/bin-standalone.js +1457 -332
- package/build/cjs/index.cjs +3379 -1333
- package/build/cjs/toolkit.cjs +2158 -373
- package/build/dts/api-call/headless-release.d.ts +56 -0
- package/build/dts/api-call/headless-release.js +135 -0
- package/build/dts/api-call/index.d.ts +9 -1
- package/build/dts/api-call/index.js +36 -8
- package/build/dts/api-call/site-files.d.ts +14 -0
- package/build/dts/api-call/site-files.js +106 -10
- package/build/dts/api-call/upload-headless-website-files.d.ts +117 -5
- package/build/dts/api-call/upload-headless-website-files.js +565 -20
- package/build/dts/api-call/upload-image-tool.d.ts +2 -0
- package/build/dts/api-call/upload-image-tool.js +8 -4
- package/build/dts/bin.d.ts +1 -1
- package/build/dts/bin.js +3 -1
- package/build/dts/code-mode/attachments.d.ts +9 -6
- package/build/dts/code-mode/attachments.js +23 -16
- package/build/dts/code-mode/index.d.ts +6 -1
- package/build/dts/code-mode/index.js +32 -12
- package/build/dts/code-mode/truncate-response.js +5 -1
- package/build/dts/config/default-config.d.ts +35 -5
- package/build/dts/config/default-config.js +339 -182
- package/build/dts/config/file-params.d.ts +22 -0
- package/build/dts/config/file-params.js +24 -0
- package/build/dts/config/param-descriptions.d.ts +14 -2
- package/build/dts/config/param-descriptions.js +33 -14
- package/build/dts/constants.d.ts +1 -0
- package/build/dts/constants.js +1 -0
- package/build/dts/docs/docs.d.ts +12 -1
- package/build/dts/docs/docs.js +274 -43
- package/build/dts/docs/fetch-article.d.ts +21 -0
- package/build/dts/docs/fetch-article.js +66 -4
- package/build/dts/docs/get-to-know.d.ts +7 -2
- package/build/dts/docs/get-to-know.js +36 -14
- package/build/dts/docs/guessed-url.d.ts +41 -0
- package/build/dts/docs/guessed-url.js +185 -0
- package/build/dts/docs/mintlify-docs.d.ts +121 -0
- package/build/dts/docs/mintlify-docs.js +391 -0
- package/build/dts/docs/semanticSearch.d.ts +2 -1
- package/build/dts/docs/semanticSearch.js +14 -7
- package/build/dts/docs/start-deploy-website.d.ts +7 -6
- package/build/dts/docs/start-deploy-website.js +13 -8
- package/build/dts/docs/start-headless-build.d.ts +8 -10
- package/build/dts/docs/start-headless-build.js +14 -28
- package/build/dts/index.d.ts +2 -2
- package/build/dts/index.js +2 -2
- package/build/dts/infra/redact-personal-data.d.ts +4 -0
- package/build/dts/infra/redact-personal-data.js +148 -0
- package/build/dts/site-widget-tools/constants.d.ts +50 -2
- package/build/dts/site-widget-tools/constants.js +65 -8
- package/build/dts/site-widget-tools/index.d.ts +1 -0
- package/build/dts/site-widget-tools/index.js +1 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
- package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
- package/build/dts/site-widget-tools/pull-site-creation-job-tool/index.js +1 -1
- package/build/dts/site-widget-tools/search-site-templates-tool/index.js +25 -2
- package/build/dts/site-widget-tools/site-builder-tool/index.js +35 -3
- package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
- package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
- package/build/dts/site-widget-tools/suggested-domain-tool/index.js +26 -12
- package/build/dts/support/index.d.ts +4 -0
- package/build/dts/support/index.js +13 -7
- package/build/dts/tool-utils.d.ts +8 -1
- package/build/dts/tool-utils.js +26 -2
- package/build/dts/toolkit-entry.d.cts +15 -10
- package/build/dts/toolkit-entry.d.ts +15 -10
- package/build/dts/toolkit.d.ts +2 -2
- package/build/dts/toolkit.js +21 -8
- package/build/dts/wix-mcp-server.d.ts +10 -1
- package/build/dts/wix-mcp-server.js +25 -7
- package/build/dts/wixel/constants.d.ts +8 -1
- package/build/dts/wixel/constants.js +8 -1
- package/build/dts/wixel/index.d.ts +1 -1
- package/build/dts/wixel/index.js +2 -2
- package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
- package/build/dts/wixel/wixel-a2a-client.js +166 -0
- package/build/dts/wixel/wixel-bi.d.ts +2 -1
- package/build/dts/wixel/wixel-bi.js +2 -1
- package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
- package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
- package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
- package/build/dts/wixel/wixel-session-tools.js +494 -599
- package/build/esm/index.js +3422 -1388
- package/build/esm/toolkit.js +2158 -373
- package/package.json +4 -4
|
@@ -5,12 +5,15 @@ import { getWidgetHtml } from '../widget-manifest.js';
|
|
|
5
5
|
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
|
|
6
6
|
import { WIXEL_WIDGET_URI, WIXEL_MCP_CSP } from './constants.js';
|
|
7
7
|
import { getStylePresetsByIds, getAsset, queryAssets } from './api.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getWixelTask, isTerminalTaskState, sendWixelMessage, summarizeTask, WixelA2AError, WIXEL_MESSAGE_MAX_LENGTH } from './wixel-a2a-client.js';
|
|
9
|
+
import { getWixelPendingQuestion, getWixelSessionActivity, getWixelSessionProjectId } from './wixel-session-entries.js';
|
|
9
10
|
import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
// (
|
|
13
|
-
//
|
|
11
|
+
// MCP tools backed by the Wixel agent's A2A endpoint: send_wixel_message
|
|
12
|
+
// (SendMessage) starts a conversation or adds a turn and returns the turn as a
|
|
13
|
+
// task; get_wixel_task (GetTask) reports it. A conversation is a contextId, a
|
|
14
|
+
// turn a taskId — both opaque. A question the agent parks on is read from the
|
|
15
|
+
// session's entries log (wixel-session-entries.ts), which a task does not
|
|
16
|
+
// carry.
|
|
14
17
|
const TOOL_META = {
|
|
15
18
|
ui: { visibility: ['app', 'model'] }
|
|
16
19
|
};
|
|
@@ -80,25 +83,6 @@ export function collectDecks(projectAssets, deckIds) {
|
|
|
80
83
|
}
|
|
81
84
|
return decks;
|
|
82
85
|
}
|
|
83
|
-
/**
|
|
84
|
-
* A reply that ends by asking the user something (a plan to approve, a
|
|
85
|
-
* choice to make) is not a delivery even when it links media — an animation
|
|
86
|
-
* plan links the source image it will animate. Asset-id markers ("Asset
|
|
87
|
-
* ready for … (id)") only appear on real deliveries; media URLs alone need
|
|
88
|
-
* this check.
|
|
89
|
-
*/
|
|
90
|
-
function isQuestionReply(text) {
|
|
91
|
-
return /\?\s*$/.test(text.trim());
|
|
92
|
-
}
|
|
93
|
-
// Image/video replies often carry no asset-id marker — just the media URL in
|
|
94
|
-
// the text: a wixstatic file, a wixmp render (img-wixmp-*/wixmp-* hosts), or a
|
|
95
|
-
// links.wixel.com short link that redirects to one. Those links are the
|
|
96
|
-
// deliverable too. Keep the hosts in step with parseWixelMediaUrl, or a URL
|
|
97
|
-
// the resolver would accept never gets extracted in the first place.
|
|
98
|
-
function extractMediaUrls(text) {
|
|
99
|
-
const matches = text.match(/https:\/\/(?:(?:static|video)\.wixstatic\.com|[a-z0-9-]+\.wixmp\.com|links\.wixel\.com\/link)\/[^\s)\]"'<>]+/g);
|
|
100
|
-
return matches ? Array.from(new Set(matches)) : [];
|
|
101
|
-
}
|
|
102
86
|
/**
|
|
103
87
|
* Run `fn` over `items` with at most `limit` in flight. `assetIds`/`mediaUrls`
|
|
104
88
|
* arrive from the model, so an unbounded Promise.all would let one call fan out
|
|
@@ -202,84 +186,59 @@ async function resolveWixelShortLink(url) {
|
|
|
202
186
|
}
|
|
203
187
|
return null;
|
|
204
188
|
}
|
|
205
|
-
// Terminal replies carry the created asset ids two ways: an HTML comment,
|
|
206
|
-
// <!-- wixel-agent-assets:["<assetId>", ...] -->, or named inline as
|
|
207
|
-
// `Asset ready for "<name>" (<assetId>)`. Both forms matter — without an id
|
|
208
|
-
// there is no entity to resolve, and a deck falls back to the one URL the
|
|
209
|
-
// reply does carry, which is its PDF export rather than its page images.
|
|
210
|
-
// Anchored on that phrasing and UUID-checked so a turnId can't slip in.
|
|
211
|
-
export function extractAssetIds(text) {
|
|
212
|
-
const ids = [];
|
|
213
|
-
const marker = text.match(/wixel-agent-assets:(\[[^\]]*\])/);
|
|
214
|
-
if (marker) {
|
|
215
|
-
try {
|
|
216
|
-
const parsed = JSON.parse(marker[1]);
|
|
217
|
-
if (Array.isArray(parsed)) {
|
|
218
|
-
ids.push(...parsed.filter((id) => typeof id === 'string'));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
catch (error) {
|
|
222
|
-
console.warn('[Wixel] unparsable asset marker', error.message);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
for (const match of text.matchAll(/Asset ready for\b[^\n]*?\(\s*([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})\s*\)/gi)) {
|
|
226
|
-
ids.push(match[1]);
|
|
227
|
-
}
|
|
228
|
-
return Array.from(new Set(ids.filter(isValidUuid)));
|
|
229
|
-
}
|
|
230
189
|
const QUESTION_TOOL_META = {
|
|
231
190
|
ui: {
|
|
232
191
|
resourceUri: WIXEL_WIDGET_URI,
|
|
233
192
|
visibility: ['app', 'model']
|
|
234
193
|
}
|
|
235
194
|
};
|
|
236
|
-
const
|
|
195
|
+
const MAX_LIVE_CARD_CONTEXTS = 500;
|
|
237
196
|
// A card that never reported done (unmounted, tab closed, timed out) must not
|
|
238
|
-
// lock the agent out of the
|
|
197
|
+
// lock the agent out of the conversation forever.
|
|
239
198
|
const LIVE_CARD_TTL_MS = 30 * 60 * 1000;
|
|
240
199
|
export function createLiveCardRegistry() {
|
|
241
200
|
const records = new Map();
|
|
242
|
-
const get = (
|
|
243
|
-
const
|
|
244
|
-
let record = records.get(
|
|
201
|
+
const get = (contextId) => records.get(contextId);
|
|
202
|
+
const forContext = (contextId) => {
|
|
203
|
+
let record = records.get(contextId);
|
|
245
204
|
if (!record) {
|
|
246
|
-
if (records.size >=
|
|
205
|
+
if (records.size >= MAX_LIVE_CARD_CONTEXTS) {
|
|
247
206
|
const oldest = records.keys().next().value;
|
|
248
207
|
if (oldest !== undefined)
|
|
249
208
|
records.delete(oldest);
|
|
250
209
|
}
|
|
251
210
|
record = {
|
|
252
|
-
|
|
253
|
-
|
|
211
|
+
tasks: new Map(),
|
|
212
|
+
finishedTasks: new Set(),
|
|
254
213
|
shownAssets: new Set(),
|
|
255
|
-
|
|
214
|
+
lastSentTask: null
|
|
256
215
|
};
|
|
257
|
-
records.set(
|
|
216
|
+
records.set(contextId, record);
|
|
258
217
|
}
|
|
259
218
|
return record;
|
|
260
219
|
};
|
|
261
220
|
/**
|
|
262
|
-
* Whether the
|
|
263
|
-
* that has not finished. Only the card's own polls mark a
|
|
264
|
-
* so when the card is gone this also
|
|
265
|
-
*
|
|
221
|
+
* Whether the task last sent in the conversation is being followed by a
|
|
222
|
+
* live card that has not finished. Only the card's own polls mark a task
|
|
223
|
+
* finished, so when the card is gone this also asks for the task: a
|
|
224
|
+
* terminal state ends the card's claim, as does the TTL.
|
|
266
225
|
*/
|
|
267
|
-
const isActive = async (
|
|
268
|
-
const record = records.get(
|
|
269
|
-
const
|
|
270
|
-
if (!record || !
|
|
226
|
+
const isActive = async (contextId, isTerminal) => {
|
|
227
|
+
const record = records.get(contextId);
|
|
228
|
+
const taskId = record?.lastSentTask;
|
|
229
|
+
if (!record || !taskId)
|
|
271
230
|
return false;
|
|
272
|
-
const renderedAt = record.
|
|
273
|
-
if (renderedAt === undefined || record.
|
|
231
|
+
const renderedAt = record.tasks.get(taskId);
|
|
232
|
+
if (renderedAt === undefined || record.finishedTasks.has(taskId)) {
|
|
274
233
|
return false;
|
|
275
234
|
}
|
|
276
235
|
if (Date.now() - renderedAt > LIVE_CARD_TTL_MS) {
|
|
277
|
-
record.
|
|
236
|
+
record.finishedTasks.add(taskId);
|
|
278
237
|
return false;
|
|
279
238
|
}
|
|
280
239
|
try {
|
|
281
|
-
if (await
|
|
282
|
-
record.
|
|
240
|
+
if (await isTerminal(taskId)) {
|
|
241
|
+
record.finishedTasks.add(taskId);
|
|
283
242
|
return false;
|
|
284
243
|
}
|
|
285
244
|
}
|
|
@@ -289,29 +248,78 @@ export function createLiveCardRegistry() {
|
|
|
289
248
|
}
|
|
290
249
|
return true;
|
|
291
250
|
};
|
|
292
|
-
return { get,
|
|
251
|
+
return { get, forContext, isActive };
|
|
293
252
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
253
|
+
/**
|
|
254
|
+
* A terminal check for isActive that reuses a task already fetched in this
|
|
255
|
+
* call instead of asking for it again.
|
|
256
|
+
*/
|
|
257
|
+
function terminalCheck(base, known) {
|
|
258
|
+
return async (taskId) => {
|
|
259
|
+
const task = known && known.id === taskId
|
|
260
|
+
? known
|
|
261
|
+
: await getWixelTask({ ...base, taskId });
|
|
262
|
+
return isTerminalTaskState(task.status?.state);
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* GetTask for a task the caller names together with its site. The service
|
|
267
|
+
* already refuses a task outside the caller's own sessions and the site they
|
|
268
|
+
* were created on; this repeats the site half here, so a task is never shown
|
|
269
|
+
* under a metaSiteId it does not carry. Answers like the service: not found.
|
|
270
|
+
*/
|
|
271
|
+
async function getSiteTask({ metaSiteId, ...params }) {
|
|
272
|
+
const task = await getWixelTask(params);
|
|
273
|
+
const taskSite = summarizeTask(task).metaSiteId;
|
|
274
|
+
if (taskSite && taskSite !== metaSiteId) {
|
|
275
|
+
throw new WixelA2AError(404, 'Unknown task id');
|
|
306
276
|
}
|
|
307
|
-
return
|
|
308
|
-
(event.type === 'reply' || event.type === 'failed'));
|
|
277
|
+
return task;
|
|
309
278
|
}
|
|
279
|
+
/** A tool result for an error the Wixel agent's A2aService answered with. */
|
|
280
|
+
function a2aErrorResult(prefix, error) {
|
|
281
|
+
return {
|
|
282
|
+
isError: true,
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: 'text',
|
|
286
|
+
text: `${prefix}: ${error.message}${error.status ? ` (HTTP ${error.status})` : ''}`
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
structuredContent: {
|
|
290
|
+
success: false,
|
|
291
|
+
error: error.message,
|
|
292
|
+
status: error.status
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const CONTEXT_MISMATCH_TEXT = 'contextId does not belong to taskId. Pass the contextId and taskId exactly as send_wixel_message returned them.';
|
|
297
|
+
function contextMismatchResult() {
|
|
298
|
+
return {
|
|
299
|
+
isError: true,
|
|
300
|
+
content: [{ type: 'text', text: CONTEXT_MISMATCH_TEXT }],
|
|
301
|
+
structuredContent: { success: false, error: 'contextId mismatch' }
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const END_TURN_SILENTLY = `End your turn now WITHOUT writing any message — no summary, no "Wixel is working on it", nothing: the card already says it, and a sentence under it is redundant. If your client forces a reply, use at most two words.`;
|
|
310
305
|
/** McpAppsBiProvider builds the widget's BI logger from
|
|
311
306
|
* widgetPayload.extras.biContext; without it every widget event is dropped. */
|
|
312
307
|
function widgetResultMeta(server) {
|
|
313
308
|
return { widgetPayload: { extras: server.getWidgetExtras() } };
|
|
314
309
|
}
|
|
310
|
+
const taskIdInput = z
|
|
311
|
+
.string()
|
|
312
|
+
.min(1)
|
|
313
|
+
.max(512)
|
|
314
|
+
.describe('taskId returned by send_wixel_message');
|
|
315
|
+
const contextIdInput = z
|
|
316
|
+
.string()
|
|
317
|
+
.min(1)
|
|
318
|
+
.max(512)
|
|
319
|
+
.describe('contextId returned by send_wixel_message');
|
|
320
|
+
const metaSiteIdInput = z
|
|
321
|
+
.string()
|
|
322
|
+
.describe('metaSiteId returned by send_wixel_message');
|
|
315
323
|
export function addGetWixelPendingQuestionTool(server, authStrategy, options = {}) {
|
|
316
324
|
const liveCards = options.liveCards ?? createLiveCardRegistry();
|
|
317
325
|
const { getSiteAuthHeaders } = authStrategy;
|
|
@@ -338,17 +346,16 @@ export function addGetWixelPendingQuestionTool(server, authStrategy, options = {
|
|
|
338
346
|
});
|
|
339
347
|
registerAppTool(server, 'get_wixel_pending_question', {
|
|
340
348
|
title: 'Get pending Wixel question',
|
|
341
|
-
description: `Fetch the structured question the Wixel agent asked in
|
|
342
|
-
Call this
|
|
349
|
+
description: `Fetch the structured question the Wixel agent asked in a turn (outline review, style selection, confirmation, choices).
|
|
350
|
+
Call this when get_wixel_task flags a question, or its reply asks the user something. In widget-capable clients this renders an interactive question widget the user can answer directly; otherwise present the returned questions as text and send the user's answer via send_wixel_message.`,
|
|
343
351
|
inputSchema: {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
.describe('Meta site ID from start_wixel_session'),
|
|
352
|
+
contextId: contextIdInput,
|
|
353
|
+
taskId: taskIdInput,
|
|
354
|
+
metaSiteId: metaSiteIdInput,
|
|
348
355
|
questionId: z
|
|
349
356
|
.string()
|
|
350
357
|
.optional()
|
|
351
|
-
.describe('Pass the questionId when
|
|
358
|
+
.describe('Pass the questionId when get_wixel_task flagged one — it binds the rendered widget to that question and returns null if it is no longer pending.')
|
|
352
359
|
},
|
|
353
360
|
annotations: {
|
|
354
361
|
readOnlyHint: true,
|
|
@@ -357,31 +364,38 @@ Call this after a reply event whenever the reply asks the user something. In wid
|
|
|
357
364
|
},
|
|
358
365
|
_meta: QUESTION_TOOL_META
|
|
359
366
|
}, async (args, extra) => {
|
|
360
|
-
setBi(extra, {
|
|
361
|
-
siteId: args.metaSiteId,
|
|
362
|
-
conversationId: args.handle
|
|
363
|
-
});
|
|
367
|
+
setBi(extra, { siteId: args.metaSiteId });
|
|
364
368
|
// Resolve auth before the try block so UnauthorizedError propagates to
|
|
365
369
|
// wrapToolCallback with the OAuth re-auth metadata (see auth/index.ts).
|
|
366
370
|
const authHeaders = await getSiteAuthHeaders(args.metaSiteId);
|
|
371
|
+
const base = {
|
|
372
|
+
authHeaders,
|
|
373
|
+
conductorOverride: options.conductorOverride
|
|
374
|
+
};
|
|
367
375
|
try {
|
|
368
|
-
// The
|
|
369
|
-
// verify
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
// The session is read from the task the agent itself vouches for;
|
|
377
|
+
// the entries reads verify it against the caller's own sessions
|
|
378
|
+
// listing as well (see isSessionVisibleToCaller).
|
|
379
|
+
const task = await getSiteTask({
|
|
380
|
+
...base,
|
|
381
|
+
taskId: args.taskId,
|
|
382
|
+
metaSiteId: args.metaSiteId
|
|
375
383
|
});
|
|
384
|
+
if (task.contextId !== args.contextId)
|
|
385
|
+
return contextMismatchResult();
|
|
386
|
+
const { sessionId, turnId } = summarizeTask(task);
|
|
387
|
+
setBi(extra, {
|
|
388
|
+
siteId: args.metaSiteId,
|
|
389
|
+
...(sessionId ? { conversationId: sessionId } : {})
|
|
390
|
+
});
|
|
391
|
+
let pending = sessionId
|
|
392
|
+
? await getWixelPendingQuestion({ ...base, sessionId, turnId })
|
|
393
|
+
: null;
|
|
376
394
|
// The live card renders this question inline; a second, standalone
|
|
377
395
|
// question widget would put it in the chat twice.
|
|
378
396
|
if (pending &&
|
|
379
397
|
options.supportsWidgets?.() === true &&
|
|
380
|
-
(await liveCards.isActive(
|
|
381
|
-
authHeaders,
|
|
382
|
-
conductorOverride: options.conductorOverride,
|
|
383
|
-
metaSiteId: args.metaSiteId
|
|
384
|
-
}))) {
|
|
398
|
+
(await liveCards.isActive(task.contextId, terminalCheck(base, task)))) {
|
|
385
399
|
console.log('[WixelResponseDump]', JSON.stringify({
|
|
386
400
|
tool: 'get_wixel_pending_question',
|
|
387
401
|
args,
|
|
@@ -393,7 +407,7 @@ Call this after a reply event whenever the reply asks the user something. In wid
|
|
|
393
407
|
{
|
|
394
408
|
type: 'text',
|
|
395
409
|
text: `The live result card already shows this question to the user and sends the answer to Wixel itself.
|
|
396
|
-
<agent_instruction>Do NOT present the question and do NOT poll
|
|
410
|
+
<agent_instruction>Do NOT present the question and do NOT poll get_wixel_task — the card follows the turn. Do not tell the user to answer either — the card is self-explanatory. ${END_TURN_SILENTLY}</agent_instruction>`
|
|
397
411
|
}
|
|
398
412
|
],
|
|
399
413
|
structuredContent: { success: true, pendingQuestion: null }
|
|
@@ -430,18 +444,19 @@ Call this after a reply event whenever the reply asks the user something. In wid
|
|
|
430
444
|
.join('\n');
|
|
431
445
|
const responseText = widgetShown
|
|
432
446
|
? `An interactive question widget is displayed to the user (questionId: ${pending.toolCallId}):\n${summary}
|
|
433
|
-
<agent_instruction>Do NOT repeat the question content as text — the user answers inside the widget, which sends the answer to Wixel itself. This is NOT a turn-taking situation: the user's widget answer
|
|
434
|
-
Therefore: briefly tell the user to answer in the widget, then keep calling
|
|
435
|
-
-
|
|
436
|
-
- When
|
|
447
|
+
<agent_instruction>Do NOT repeat the question content as text — the user answers inside the widget, which sends the answer to Wixel itself. This is NOT a turn-taking situation: the result of the user's widget answer reaches YOU only through get_wixel_task — nothing wakes you if you stop, and ending your turn BREAKS the flow (the result would never be delivered).
|
|
448
|
+
Therefore: briefly tell the user to answer in the widget, then keep calling get_wixel_task (taskId: "${args.taskId}", metaSiteId: "${args.metaSiteId}", waitSeconds: 25) in a continuous loop, and do NOT end your turn until it reports the turn done, however long it takes (the user may think for a while, then generation takes minutes). If it names a newer taskId (the widget's answer opened a new turn), follow that one.
|
|
449
|
+
- Polls that flag questionId ${pending.toolCallId} mean the user has not answered THIS question yet — do NOT call get_wixel_pending_question again for it, just keep polling.
|
|
450
|
+
- When the turn is done: if the response flags a NEW interactive question, call get_wixel_pending_question again (it renders the next widget). Otherwise present the reply to the user as a normal chat message, always including the "Edit in Wixel" link when provided.</agent_instruction>`
|
|
437
451
|
: `The Wixel agent asked:\n${summary}
|
|
438
|
-
<agent_instruction>Present the question(s) with their options/content to the user, then send their answer as plain text via send_wixel_message (
|
|
452
|
+
<agent_instruction>Present the question(s) with their options/content to the user, then send their answer as plain text via send_wixel_message (contextId: "${args.contextId}", metaSiteId: "${args.metaSiteId}") and poll get_wixel_task for the taskId it returns.</agent_instruction>`;
|
|
439
453
|
return {
|
|
440
454
|
isError: false,
|
|
441
455
|
content: [{ type: 'text', text: responseText }],
|
|
442
456
|
structuredContent: {
|
|
443
457
|
success: true,
|
|
444
|
-
|
|
458
|
+
contextId: args.contextId,
|
|
459
|
+
taskId: args.taskId,
|
|
445
460
|
metaSiteId: args.metaSiteId,
|
|
446
461
|
pendingQuestion: pending
|
|
447
462
|
},
|
|
@@ -449,6 +464,9 @@ Therefore: briefly tell the user to answer in the widget, then keep calling list
|
|
|
449
464
|
};
|
|
450
465
|
}
|
|
451
466
|
catch (error) {
|
|
467
|
+
if (error instanceof WixelA2AError) {
|
|
468
|
+
return a2aErrorResult('Could not read the Wixel task', error);
|
|
469
|
+
}
|
|
452
470
|
rethrowIfUnauthorized(error);
|
|
453
471
|
console.error('❌ Error getting Wixel pending question:', error);
|
|
454
472
|
const errorMessage = error.message;
|
|
@@ -460,127 +478,36 @@ Therefore: briefly tell the user to answer in the widget, then keep calling list
|
|
|
460
478
|
}
|
|
461
479
|
});
|
|
462
480
|
}
|
|
463
|
-
export function addStartWixelSessionTool(server, authStrategy, options = {}) {
|
|
464
|
-
const { getAccountAuthHeaders, getSiteAuthHeaders } = authStrategy;
|
|
465
|
-
registerAppTool(server, 'start_wixel_session', {
|
|
466
|
-
title: 'Start Wixel session',
|
|
467
|
-
description: `Start (or resume) a session with Wixel — Wix's AI creative studio that GENERATES visual assets from text prompts: images, logos, slide decks / presentations, videos, documents, and event invitations. Use this flow for any "create/generate an image / logo / slides / video" request. It is NOT a website builder.
|
|
468
|
-
Call this ONCE per conversation and persist the returned handle + metaSiteId (Wixel's project identifiers) — they are required by send_wixel_message and list_wixel_events.
|
|
469
|
-
Without arguments Wixel provisions a new project and session. Pass metaSiteId to reuse an existing project; pass sessionId + metaSiteId to resume a session created by this API.`,
|
|
470
|
-
inputSchema: {
|
|
471
|
-
sessionId: z
|
|
472
|
-
.string()
|
|
473
|
-
.optional()
|
|
474
|
-
.describe('Existing session ID to resume (requires metaSiteId as well)'),
|
|
475
|
-
metaSiteId: z
|
|
476
|
-
.string()
|
|
477
|
-
.optional()
|
|
478
|
-
.describe('Existing meta site ID to reuse its project'),
|
|
479
|
-
externalId: z
|
|
480
|
-
.string()
|
|
481
|
-
.optional()
|
|
482
|
-
.describe('Stable identifier of the caller conversation (for diagnostics)'),
|
|
483
|
-
workspace: z
|
|
484
|
-
.enum(['UNKNOWN', 'BRAND'])
|
|
485
|
-
.optional()
|
|
486
|
-
.describe('Workspace to provision; omit for the default')
|
|
487
|
-
},
|
|
488
|
-
annotations: {
|
|
489
|
-
readOnlyHint: false,
|
|
490
|
-
destructiveHint: false,
|
|
491
|
-
openWorldHint: false
|
|
492
|
-
},
|
|
493
|
-
_meta: TOOL_META
|
|
494
|
-
}, async (args, extra) => {
|
|
495
|
-
// Both are absent on a fresh session; refined again after the call.
|
|
496
|
-
setBi(extra, {
|
|
497
|
-
...(args.metaSiteId ? { siteId: args.metaSiteId } : {}),
|
|
498
|
-
...(args.sessionId ? { conversationId: args.sessionId } : {})
|
|
499
|
-
});
|
|
500
|
-
if (args.sessionId && !args.metaSiteId) {
|
|
501
|
-
return {
|
|
502
|
-
isError: true,
|
|
503
|
-
content: [
|
|
504
|
-
{
|
|
505
|
-
type: 'text',
|
|
506
|
-
text: 'Invalid input: resuming a session (sessionId) also requires the metaSiteId returned by the original start_wixel_session call.'
|
|
507
|
-
}
|
|
508
|
-
],
|
|
509
|
-
structuredContent: {
|
|
510
|
-
success: false,
|
|
511
|
-
error: 'sessionId requires metaSiteId'
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
const authHeaders = args.metaSiteId
|
|
516
|
-
? await getSiteAuthHeaders(args.metaSiteId)
|
|
517
|
-
: await getAccountAuthHeaders();
|
|
518
|
-
try {
|
|
519
|
-
const session = await startWixelSession({
|
|
520
|
-
authHeaders,
|
|
521
|
-
conductorOverride: options.conductorOverride,
|
|
522
|
-
sessionId: args.sessionId,
|
|
523
|
-
metaSiteId: args.metaSiteId,
|
|
524
|
-
externalId: args.externalId,
|
|
525
|
-
workspace: args.workspace
|
|
526
|
-
});
|
|
527
|
-
setBi(extra, {
|
|
528
|
-
siteId: session.metaSiteId ?? args.metaSiteId,
|
|
529
|
-
conversationId: session.handle ?? args.sessionId
|
|
530
|
-
});
|
|
531
|
-
console.log('[WixelResponseDump]', JSON.stringify({ tool: 'start_wixel_session', args, session }));
|
|
532
|
-
return {
|
|
533
|
-
isError: false,
|
|
534
|
-
content: [
|
|
535
|
-
{
|
|
536
|
-
type: 'text',
|
|
537
|
-
text: `Wixel session ${session.status ?? 'started'} — handle: ${session.handle}, metaSiteId: ${session.metaSiteId}.
|
|
538
|
-
<agent_instruction>Persist handle and metaSiteId for this conversation. Send prompts with send_wixel_message, then poll list_wixel_events (advancing the cursor) until a "reply" or "failed" event arrives for the returned turnId.</agent_instruction>`
|
|
539
|
-
}
|
|
540
|
-
],
|
|
541
|
-
structuredContent: {
|
|
542
|
-
success: true,
|
|
543
|
-
handle: session.handle,
|
|
544
|
-
sessionId: session.sessionId,
|
|
545
|
-
metaSiteId: session.metaSiteId,
|
|
546
|
-
status: session.status,
|
|
547
|
-
expiresAt: session.expiresAt ?? null
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
rethrowIfUnauthorized(error);
|
|
553
|
-
console.error('❌ Error starting Wixel session:', error);
|
|
554
|
-
const errorMessage = error.message;
|
|
555
|
-
return {
|
|
556
|
-
isError: true,
|
|
557
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
558
|
-
structuredContent: { success: false, error: errorMessage }
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
481
|
export function addSendWixelMessageTool(server, authStrategy, options = {}) {
|
|
564
482
|
const liveCards = options.liveCards ?? createLiveCardRegistry();
|
|
565
|
-
const { getSiteAuthHeaders } = authStrategy;
|
|
483
|
+
const { getAccountAuthHeaders, getSiteAuthHeaders } = authStrategy;
|
|
566
484
|
registerAppTool(server, 'send_wixel_message', {
|
|
567
485
|
title: 'Send Wixel message',
|
|
568
|
-
description: `Send a
|
|
569
|
-
|
|
486
|
+
description: `Send a request to Wixel — Wix's AI creative studio that GENERATES visual assets from text prompts: images, logos, slide decks / presentations, videos, documents, and event invitations. Use it for any "create/generate an image / logo / slides / video" request, and for refinements ("make the logo blue"). It is NOT a website builder.
|
|
487
|
+
The first call starts a conversation: omit contextId (pass metaSiteId only to reuse an existing Wixel project). It returns contextId, taskId and metaSiteId — persist contextId and pass only contextId on every later message of the conversation (it is already bound to its site).
|
|
488
|
+
The turn runs asynchronously — the response only means it was ACCEPTED. Obtain the agent's answer (including links to the generated assets) with get_wixel_task for the returned taskId.
|
|
570
489
|
Send the user's request as THEY phrased it. Do NOT add creative direction they did not give — style, palette, mood, tone, audience, aspect ratio, slide count or slide topics. Wixel asks the user about those itself, and widget-capable clients render the question as an interactive widget (get_wixel_pending_question), so details you invent silently overwrite the choices the user was about to make. Rephrasing for clarity or translating is fine; filling in the blanks is not.`,
|
|
571
490
|
inputSchema: {
|
|
572
|
-
handle: z.string().describe('Session handle from start_wixel_session'),
|
|
573
|
-
metaSiteId: z
|
|
574
|
-
.string()
|
|
575
|
-
.describe('Meta site ID from start_wixel_session'),
|
|
576
491
|
text: z
|
|
577
492
|
.string()
|
|
578
|
-
.
|
|
493
|
+
.min(1)
|
|
494
|
+
.max(WIXEL_MESSAGE_MAX_LENGTH)
|
|
579
495
|
.describe("The user's request in their own words — do not add style, palette, audience or content details they did not give"),
|
|
580
|
-
|
|
496
|
+
contextId: z
|
|
497
|
+
.string()
|
|
498
|
+
.min(1)
|
|
499
|
+
.max(512)
|
|
500
|
+
.optional()
|
|
501
|
+
.describe('The conversation, as a previous send_wixel_message returned it. Omit to start a new conversation.'),
|
|
502
|
+
metaSiteId: z
|
|
503
|
+
.string()
|
|
504
|
+
.optional()
|
|
505
|
+
.describe('First message only: an existing meta site ID whose Wixel project the new conversation reuses. Ignored with contextId.'),
|
|
506
|
+
externalId: z
|
|
581
507
|
.string()
|
|
508
|
+
.max(256)
|
|
582
509
|
.optional()
|
|
583
|
-
.describe('
|
|
510
|
+
.describe('Stable identifier of the caller conversation (for diagnostics)')
|
|
584
511
|
},
|
|
585
512
|
annotations: {
|
|
586
513
|
readOnlyHint: false,
|
|
@@ -589,72 +516,74 @@ Send the user's request as THEY phrased it. Do NOT add creative direction they d
|
|
|
589
516
|
},
|
|
590
517
|
_meta: TOOL_META
|
|
591
518
|
}, async (args, extra) => {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
519
|
+
// A later message names only its conversation: the agent reads the site
|
|
520
|
+
// from the contextId and checks it against the caller's own sites, so
|
|
521
|
+
// the account identity is enough. A site identity is only needed when a
|
|
522
|
+
// first message reuses an existing site's project.
|
|
523
|
+
const reuseSite = args.contextId ? undefined : args.metaSiteId;
|
|
524
|
+
setBi(extra, reuseSite ? { siteId: reuseSite } : {});
|
|
525
|
+
const authHeaders = reuseSite
|
|
526
|
+
? await getSiteAuthHeaders(reuseSite)
|
|
527
|
+
: await getAccountAuthHeaders();
|
|
597
528
|
try {
|
|
598
529
|
// The user's text goes through as typed — nothing is appended. An
|
|
599
530
|
// earlier hint here asked Wixel to stop and ask about style/palette
|
|
600
531
|
// whenever a request left them open, which is why MCP users got
|
|
601
532
|
// questions the Wixel app never asks. Wixel's own flows (a deck's
|
|
602
533
|
// outline and style review) still ask, and the live card renders them.
|
|
603
|
-
const
|
|
604
|
-
const result = await sendWixelSessionMessage({
|
|
534
|
+
const task = await sendWixelMessage({
|
|
605
535
|
authHeaders,
|
|
606
536
|
conductorOverride: options.conductorOverride,
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
537
|
+
text: args.text,
|
|
538
|
+
contextId: args.contextId,
|
|
539
|
+
metaSiteId: reuseSite,
|
|
540
|
+
externalId: args.externalId
|
|
541
|
+
});
|
|
542
|
+
const summary = summarizeTask(task);
|
|
543
|
+
const metaSiteId = summary.metaSiteId ?? reuseSite ?? null;
|
|
544
|
+
setBi(extra, {
|
|
545
|
+
...(metaSiteId ? { siteId: metaSiteId } : {}),
|
|
546
|
+
...(summary.sessionId ? { conversationId: summary.sessionId } : {})
|
|
611
547
|
});
|
|
612
548
|
console.log('[WixelResponseDump]', JSON.stringify({
|
|
613
549
|
tool: 'send_wixel_message',
|
|
614
|
-
args: { ...args, text: text.slice(0, 400) },
|
|
615
|
-
|
|
550
|
+
args: { ...args, text: args.text.slice(0, 400) },
|
|
551
|
+
task: {
|
|
552
|
+
id: task.id,
|
|
553
|
+
contextId: task.contextId,
|
|
554
|
+
state: summary.state,
|
|
555
|
+
metadata: task.metadata
|
|
556
|
+
}
|
|
616
557
|
}));
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
{
|
|
622
|
-
type: 'text',
|
|
623
|
-
text: `Wixel rejected the message: ${result.error ?? 'unknown error'} (${result.code ?? 'no code'})`
|
|
624
|
-
}
|
|
625
|
-
],
|
|
626
|
-
structuredContent: {
|
|
627
|
-
success: false,
|
|
628
|
-
error: result.error,
|
|
629
|
-
code: result.code
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
if (result.turnId) {
|
|
634
|
-
liveCards.forHandle(args.handle).lastSentTurn = result.turnId;
|
|
635
|
-
}
|
|
558
|
+
liveCards.forContext(task.contextId).lastSentTask = task.id;
|
|
559
|
+
const steeredNote = summary.steered
|
|
560
|
+
? "\nA turn was already running, so the message was added to it: this is that turn's task."
|
|
561
|
+
: '';
|
|
636
562
|
// Widget clients get a live result card that follows the turn itself
|
|
637
563
|
// (progress, questions, final asset), so the agent renders it once
|
|
638
564
|
// and stops — no polling loop of tool calls in the conversation.
|
|
639
565
|
const instruction = options.supportsWidgets?.()
|
|
640
|
-
? `<agent_instruction>Now call get_wixel_asset_result with
|
|
641
|
-
: `<agent_instruction>Poll
|
|
642
|
-
|
|
566
|
+
? `<agent_instruction>Now call get_wixel_asset_result with contextId: "${task.contextId}", taskId: "${task.id}", metaSiteId: "${metaSiteId}", prompt: <the user's request as they phrased it>. It renders a live result card that shows progress, asks the user any question Wixel has, and displays the final asset. Then end your turn — do NOT poll get_wixel_task and do NOT call get_wixel_pending_question, and do NOT write a message after the card (it explains itself).</agent_instruction>`
|
|
567
|
+
: `<agent_instruction>Poll get_wixel_task (taskId: "${task.id}", metaSiteId: "${metaSiteId}", waitSeconds: 25) until it reports the turn done. Turns can take minutes for generation tasks — keep polling.
|
|
568
|
+
If it flags an interactive question, call get_wixel_pending_question (contextId: "${task.contextId}", taskId: "${task.id}", metaSiteId: "${metaSiteId}") and follow its response instruction.</agent_instruction>`;
|
|
643
569
|
const structuredContent = {
|
|
644
570
|
success: true,
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
571
|
+
contextId: task.contextId,
|
|
572
|
+
taskId: task.id,
|
|
573
|
+
metaSiteId,
|
|
574
|
+
sessionId: summary.sessionId,
|
|
575
|
+
state: summary.state,
|
|
576
|
+
steered: summary.steered
|
|
648
577
|
};
|
|
649
578
|
return {
|
|
650
579
|
isError: false,
|
|
651
580
|
content: [
|
|
652
581
|
{
|
|
653
582
|
type: 'text',
|
|
654
|
-
text: `Turn accepted —
|
|
583
|
+
text: `Turn accepted — contextId: ${task.contextId}, taskId: ${task.id}, metaSiteId: ${metaSiteId}.${steeredNote}
|
|
655
584
|
${instruction}`
|
|
656
585
|
},
|
|
657
|
-
// The widgets send answers through this tool and need the
|
|
586
|
+
// The widgets send answers through this tool and need the taskId
|
|
658
587
|
// back; hosts that drop structuredContent get it from this item.
|
|
659
588
|
{ type: 'text', text: JSON.stringify(structuredContent) }
|
|
660
589
|
],
|
|
@@ -662,6 +591,9 @@ ${instruction}`
|
|
|
662
591
|
};
|
|
663
592
|
}
|
|
664
593
|
catch (error) {
|
|
594
|
+
if (error instanceof WixelA2AError) {
|
|
595
|
+
return a2aErrorResult('Wixel rejected the message', error);
|
|
596
|
+
}
|
|
665
597
|
rethrowIfUnauthorized(error);
|
|
666
598
|
console.error('❌ Error sending Wixel message:', error);
|
|
667
599
|
const errorMessage = error.message;
|
|
@@ -673,37 +605,24 @@ ${instruction}`
|
|
|
673
605
|
}
|
|
674
606
|
});
|
|
675
607
|
}
|
|
676
|
-
|
|
608
|
+
const LONG_POLL_INTERVAL_MS = 2500;
|
|
609
|
+
const LONG_POLL_MAX_SECONDS = 25;
|
|
610
|
+
export function addGetWixelTaskTool(server, authStrategy, options = {}) {
|
|
677
611
|
const liveCards = options.liveCards ?? createLiveCardRegistry();
|
|
678
612
|
const { getSiteAuthHeaders } = authStrategy;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const lastCursorByHandle = new Map();
|
|
684
|
-
registerAppTool(server, 'list_wixel_events', {
|
|
685
|
-
title: 'List Wixel events',
|
|
686
|
-
description: `Read durable turn events from a Wixel Agent session inbox. Pass the cursor returned by the previous call as "since" to only receive newer events.
|
|
687
|
-
Event types: "reply" (the agent's answer), "failed" (the turn failed), and "user" (only with historyMode FULL). Poll until the awaited turnId gets a reply/failed event.`,
|
|
613
|
+
registerAppTool(server, 'get_wixel_task', {
|
|
614
|
+
title: 'Get Wixel task',
|
|
615
|
+
description: `Report a Wixel turn (a task returned by send_wixel_message): still working, parked on a question for the user, done with the agent's reply and the generated assets, or failed.
|
|
616
|
+
Poll it with waitSeconds: 25 until it reports the turn done.`,
|
|
688
617
|
inputSchema: {
|
|
689
|
-
|
|
690
|
-
metaSiteId:
|
|
691
|
-
.string()
|
|
692
|
-
.describe('Meta site ID from start_wixel_session'),
|
|
693
|
-
since: z
|
|
694
|
-
.string()
|
|
695
|
-
.optional()
|
|
696
|
-
.describe('Cursor returned by the previous list_wixel_events call. May be omitted — the server remembers the last cursor it served for this handle and continues from there.'),
|
|
697
|
-
historyMode: z
|
|
698
|
-
.enum(['REPLIES_ONLY', 'FULL'])
|
|
699
|
-
.optional()
|
|
700
|
-
.describe('REPLIES_ONLY (default) returns terminal events only; FULL also includes echoed user messages'),
|
|
618
|
+
taskId: taskIdInput,
|
|
619
|
+
metaSiteId: metaSiteIdInput,
|
|
701
620
|
waitSeconds: z
|
|
702
621
|
.number()
|
|
703
622
|
.min(0)
|
|
704
|
-
.max(
|
|
623
|
+
.max(LONG_POLL_MAX_SECONDS)
|
|
705
624
|
.optional()
|
|
706
|
-
.describe('Long-poll:
|
|
625
|
+
.describe('Long-poll: while the turn is still working, hold the request up to this many seconds until it ends. Recommended: 25 while waiting on generation or on a widget answer.')
|
|
707
626
|
},
|
|
708
627
|
annotations: {
|
|
709
628
|
readOnlyHint: true,
|
|
@@ -712,39 +631,38 @@ Event types: "reply" (the agent's answer), "failed" (the turn failed), and "user
|
|
|
712
631
|
},
|
|
713
632
|
_meta: TOOL_META
|
|
714
633
|
}, async (args, extra) => {
|
|
715
|
-
setBi(extra, {
|
|
716
|
-
siteId: args.metaSiteId,
|
|
717
|
-
conversationId: args.handle
|
|
718
|
-
});
|
|
634
|
+
setBi(extra, { siteId: args.metaSiteId });
|
|
719
635
|
const authHeaders = await getSiteAuthHeaders(args.metaSiteId);
|
|
636
|
+
const base = {
|
|
637
|
+
authHeaders,
|
|
638
|
+
conductorOverride: options.conductorOverride
|
|
639
|
+
};
|
|
720
640
|
try {
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
handle: args.handle,
|
|
726
|
-
metaSiteId: args.metaSiteId,
|
|
727
|
-
since,
|
|
728
|
-
historyMode: args.historyMode
|
|
641
|
+
const fetchTask = () => getSiteTask({
|
|
642
|
+
...base,
|
|
643
|
+
taskId: args.taskId,
|
|
644
|
+
metaSiteId: args.metaSiteId
|
|
729
645
|
});
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
646
|
+
let task = await fetchTask();
|
|
647
|
+
let summary = summarizeTask(task);
|
|
648
|
+
setBi(extra, {
|
|
649
|
+
siteId: args.metaSiteId,
|
|
650
|
+
...(summary.sessionId ? { conversationId: summary.sessionId } : {})
|
|
651
|
+
});
|
|
652
|
+
// A live result card is following the conversation's latest turn:
|
|
653
|
+
// the agent has nothing to do here, and text instructions alone don't
|
|
654
|
+
// stop its polling loop — so refuse the call. The tool works again
|
|
655
|
+
// once that turn has ended (the card saw it, or the task says so
|
|
656
|
+
// when the card is gone).
|
|
735
657
|
if (options.supportsWidgets?.() === true &&
|
|
736
|
-
(await liveCards.isActive(
|
|
737
|
-
|
|
738
|
-
conductorOverride: options.conductorOverride,
|
|
739
|
-
metaSiteId: args.metaSiteId
|
|
740
|
-
}))) {
|
|
741
|
-
console.log('[WixelResponseDump]', JSON.stringify({ tool: 'list_wixel_events', args, refused: true }));
|
|
658
|
+
(await liveCards.isActive(task.contextId, terminalCheck(base, task)))) {
|
|
659
|
+
console.log('[WixelResponseDump]', JSON.stringify({ tool: 'get_wixel_task', args, refused: true }));
|
|
742
660
|
return {
|
|
743
661
|
isError: true,
|
|
744
662
|
content: [
|
|
745
663
|
{
|
|
746
664
|
type: 'text',
|
|
747
|
-
text: `Not needed: the live result card is following this generation and will display progress, any question, and the final result by itself. This is not a failure — there is nothing to wait for here. Do NOT call
|
|
665
|
+
text: `Not needed: the live result card is following this generation and will display progress, any question, and the final result by itself. This is not a failure — there is nothing to wait for here. Do NOT call get_wixel_task, get_wixel_pending_question or get_wixel_asset_result again for this generation. ${END_TURN_SILENTLY}`
|
|
748
666
|
}
|
|
749
667
|
],
|
|
750
668
|
structuredContent: {
|
|
@@ -754,181 +672,41 @@ Event types: "reply" (the agent's answer), "failed" (the turn failed), and "user
|
|
|
754
672
|
}
|
|
755
673
|
};
|
|
756
674
|
}
|
|
757
|
-
|
|
758
|
-
// Long-poll: hold the request while the inbox is empty so callers can
|
|
675
|
+
// Long-poll: hold the request while the turn works so callers can
|
|
759
676
|
// wait on slow turns (generation, widget answers) without hammering.
|
|
760
|
-
const waitDeadline = Date.now() +
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (result.error) {
|
|
769
|
-
return {
|
|
770
|
-
isError: true,
|
|
771
|
-
content: [
|
|
772
|
-
{
|
|
773
|
-
type: 'text',
|
|
774
|
-
text: `Failed to list events: ${result.error} (${result.code ?? 'no code'})`
|
|
775
|
-
}
|
|
776
|
-
],
|
|
777
|
-
structuredContent: {
|
|
778
|
-
success: false,
|
|
779
|
-
error: result.error,
|
|
780
|
-
code: result.code
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
const events = result.events ?? [];
|
|
785
|
-
if (result.cursor) {
|
|
786
|
-
lastCursorByHandle.set(args.handle, result.cursor);
|
|
677
|
+
const waitDeadline = Date.now() +
|
|
678
|
+
Math.min(Math.max(args.waitSeconds ?? 0, 0), LONG_POLL_MAX_SECONDS) *
|
|
679
|
+
1000;
|
|
680
|
+
while (!summary.done &&
|
|
681
|
+
Date.now() + LONG_POLL_INTERVAL_MS <= waitDeadline) {
|
|
682
|
+
await new Promise((resolve) => setTimeout(resolve, LONG_POLL_INTERVAL_MS));
|
|
683
|
+
task = await fetchTask();
|
|
684
|
+
summary = summarizeTask(task);
|
|
787
685
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
// Resolve an "edit in Wixel" link once the reply references created
|
|
806
|
-
// assets: the session is bound to its Wixel project, which is the
|
|
807
|
-
// editor deep-link target. Some replies carry asset-id markers,
|
|
808
|
-
// others just embed the media URL — both count as delivered.
|
|
809
|
-
const replyTexts = events
|
|
810
|
-
.filter((event) => event.type === 'reply' && event.text)
|
|
811
|
-
.map((event) => event.text);
|
|
812
|
-
deliveredAssetIds = replyTexts.flatMap(extractAssetIds);
|
|
813
|
-
// Same rule as the live card: a reply that asks the user something
|
|
814
|
-
// (e.g. an animation plan linking its source image) delivers
|
|
815
|
-
// nothing by its media URLs alone.
|
|
816
|
-
deliveredMediaUrls = replyTexts
|
|
817
|
-
.filter((text) => !isQuestionReply(text))
|
|
818
|
-
.flatMap(extractMediaUrls);
|
|
819
|
-
if (deliveredAssetIds.length || deliveredMediaUrls.length) {
|
|
820
|
-
deliveredAssets = true;
|
|
821
|
-
// Delivered by a turn a live card follows: the card shows them,
|
|
822
|
-
// so a get_wixel_asset_result for these ids is a duplicate. Only
|
|
823
|
-
// that turn's ids — a batch may also carry a turn no card follows.
|
|
824
|
-
const record = liveCards.get(args.handle);
|
|
825
|
-
if (record) {
|
|
826
|
-
events
|
|
827
|
-
.filter((event) => event.type === 'reply' &&
|
|
828
|
-
!!event.text &&
|
|
829
|
-
!!event.turnId &&
|
|
830
|
-
record.turns.has(event.turnId))
|
|
831
|
-
.flatMap((event) => [
|
|
832
|
-
...extractAssetIds(event.text).slice(0, MAX_RESULT_ITEMS),
|
|
833
|
-
...extractMediaUrls(event.text).slice(0, MAX_RESULT_ITEMS)
|
|
834
|
-
])
|
|
835
|
-
.forEach((item) => record.shownAssets.add(item));
|
|
836
|
-
}
|
|
837
|
-
try {
|
|
838
|
-
const projectId = await getWixelSessionProjectId({
|
|
839
|
-
authHeaders,
|
|
840
|
-
conductorOverride: options.conductorOverride,
|
|
841
|
-
sessionId: args.handle
|
|
842
|
-
});
|
|
843
|
-
if (projectId) {
|
|
844
|
-
editorUrl = buildWixelEditorUrl(args.metaSiteId, projectId);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
catch (projectError) {
|
|
848
|
-
console.warn('[Wixel] editor link resolution failed:', projectError.message);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
if (hasReply || events.length === 0) {
|
|
853
|
-
try {
|
|
854
|
-
if (hasReply) {
|
|
855
|
-
if (!deliveredAssets) {
|
|
856
|
-
pendingQuestion = await getWixelPendingQuestion({
|
|
857
|
-
authHeaders,
|
|
858
|
-
conductorOverride: options.conductorOverride,
|
|
859
|
-
sessionId: args.handle
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
// On an empty poll only a parked ask_user counts — a question
|
|
865
|
-
// already handled (policy fallback or answered in the Wixel UI)
|
|
866
|
-
// must not resurface while the turn keeps generating. The same
|
|
867
|
-
// entries read also tells whether a turn is actively running,
|
|
868
|
-
// so the caller knows to keep polling rather than turn to the
|
|
869
|
-
// user.
|
|
870
|
-
const activity = await getWixelSessionActivity({
|
|
871
|
-
authHeaders,
|
|
872
|
-
conductorOverride: options.conductorOverride,
|
|
873
|
-
sessionId: args.handle
|
|
874
|
-
});
|
|
875
|
-
pendingQuestion = activity.parkedQuestion;
|
|
876
|
-
turnInProgress = activity.turnInProgress;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
catch (questionError) {
|
|
880
|
-
console.warn('[Wixel] pending question fetch failed:', questionError.message);
|
|
881
|
-
}
|
|
882
|
-
if (pendingQuestion) {
|
|
883
|
-
questionInstruction = `
|
|
884
|
-
IMPORTANT: the agent is asking the user an interactive question (${pendingQuestion.questions
|
|
885
|
-
.map((question) => question.kind)
|
|
886
|
-
.join(', ')}; questionId: ${pendingQuestion.toolCallId}). If you have NOT yet displayed this questionId, you MUST now call get_wixel_pending_question (handle: "${args.handle}", metaSiteId: "${args.metaSiteId}", questionId: "${pendingQuestion.toolCallId}") and follow its response instruction BEFORE responding to the user — do not present the question yourself. If you already displayed this questionId, the user simply hasn't answered it yet — do NOT end your turn (the answer arrives only as an event to you; stopping breaks the flow): keep polling list_wixel_events (waitSeconds: 25) without re-calling get_wixel_pending_question.`;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
const summary = events.length
|
|
890
|
-
? events
|
|
891
|
-
.map((event) => `[${event.type}] turnId: ${event.turnId ?? '-'}${event.text ? ` — ${event.text}` : ''}`)
|
|
892
|
-
.join('\n')
|
|
893
|
-
: pendingQuestion
|
|
894
|
-
? 'The turn is parked on an interactive question awaiting the user.'
|
|
895
|
-
: turnInProgress
|
|
896
|
-
? 'No new events yet — Wixel is actively working on the current turn.'
|
|
897
|
-
: 'No new events yet — call list_wixel_events again with the same cursor.';
|
|
898
|
-
// An empty poll while a turn is generating must not read as "waiting
|
|
899
|
-
// on the user" — the caller's next step is to keep polling, nothing
|
|
900
|
-
// else.
|
|
901
|
-
const workingInstruction = !events.length && !pendingQuestion && turnInProgress
|
|
902
|
-
? `
|
|
903
|
-
<agent_instruction>Wixel is actively generating — the user does NOT need to do anything. Do NOT end your turn and do NOT ask the user for input: call list_wixel_events again (handle: "${args.handle}", metaSiteId: "${args.metaSiteId}", same cursor, waitSeconds: 25) and repeat until a "reply" or "failed" event arrives, however many polls it takes.</agent_instruction>`
|
|
904
|
-
: '';
|
|
905
|
-
const editorLine = deliveredAssets
|
|
906
|
-
? `${editorUrl ? `\nEdit in Wixel: ${editorUrl}` : ''}\n<agent_instruction>The asset is READY — this reply is the final deliverable. You MUST now call get_wixel_asset_result with EXACTLY these arguments: handle: "${args.handle}", metaSiteId: "${args.metaSiteId}", assetIds: ${JSON.stringify(deliveredAssetIds)}, mediaUrls: ${JSON.stringify(deliveredMediaUrls)}, prompt: <the user's original request>. In widget-capable clients it renders the result card (preview + edit/download actions); otherwise present the reply as text${editorUrl ? ' and ALWAYS include the Edit in Wixel link' : ''}. Do NOT call get_wixel_pending_question now — if the reply offers optional follow-ups (restyle, edits), mention them as text.</agent_instruction>`
|
|
907
|
-
: '';
|
|
908
|
-
return {
|
|
909
|
-
isError: false,
|
|
910
|
-
content: [
|
|
911
|
-
{
|
|
912
|
-
type: 'text',
|
|
913
|
-
text: `${summary}\ncursor: ${result.cursor}${editorLine}${questionInstruction}${workingInstruction}`
|
|
914
|
-
}
|
|
915
|
-
],
|
|
916
|
-
structuredContent: {
|
|
917
|
-
success: true,
|
|
918
|
-
events,
|
|
919
|
-
cursor: result.cursor,
|
|
920
|
-
hasPendingQuestion: !!pendingQuestion,
|
|
921
|
-
pendingQuestionId: pendingQuestion?.toolCallId ?? null,
|
|
922
|
-
turnInProgress,
|
|
923
|
-
editorUrl,
|
|
924
|
-
assetIds: deliveredAssetIds,
|
|
925
|
-
mediaUrls: deliveredMediaUrls
|
|
926
|
-
}
|
|
927
|
-
};
|
|
686
|
+
const result = await describeTask({
|
|
687
|
+
base,
|
|
688
|
+
task,
|
|
689
|
+
summary,
|
|
690
|
+
liveCards,
|
|
691
|
+
metaSiteId: args.metaSiteId
|
|
692
|
+
});
|
|
693
|
+
console.log('[WixelResponseDump]', JSON.stringify({
|
|
694
|
+
tool: 'get_wixel_task',
|
|
695
|
+
args,
|
|
696
|
+
state: summary.state,
|
|
697
|
+
replyText: summary.replyText,
|
|
698
|
+
assetIds: summary.assetIds,
|
|
699
|
+
mediaUrls: summary.mediaUrls,
|
|
700
|
+
pendingQuestionId: result.structuredContent.pendingQuestionId
|
|
701
|
+
}).slice(0, 30000));
|
|
702
|
+
return result;
|
|
928
703
|
}
|
|
929
704
|
catch (error) {
|
|
705
|
+
if (error instanceof WixelA2AError) {
|
|
706
|
+
return a2aErrorResult('Could not read the Wixel task', error);
|
|
707
|
+
}
|
|
930
708
|
rethrowIfUnauthorized(error);
|
|
931
|
-
console.error('❌ Error
|
|
709
|
+
console.error('❌ Error getting Wixel task:', error);
|
|
932
710
|
const errorMessage = error.message;
|
|
933
711
|
return {
|
|
934
712
|
isError: true,
|
|
@@ -938,6 +716,125 @@ IMPORTANT: the agent is asking the user an interactive question (${pendingQuesti
|
|
|
938
716
|
}
|
|
939
717
|
});
|
|
940
718
|
}
|
|
719
|
+
/** get_wixel_task's answer for a task it has read. */
|
|
720
|
+
async function describeTask({ base, task, summary, liveCards, metaSiteId }) {
|
|
721
|
+
const { taskId, contextId, sessionId } = summary;
|
|
722
|
+
const ids = `contextId: "${contextId}", taskId: "${taskId}", metaSiteId: "${metaSiteId}"`;
|
|
723
|
+
const failed = summary.done && summary.state !== 'TASK_STATE_COMPLETED';
|
|
724
|
+
// A reply that names assets or links media IS the deliverable — the flow
|
|
725
|
+
// is complete, so any follow-up question (e.g. "pick a new style?") must
|
|
726
|
+
// not spawn another widget.
|
|
727
|
+
const delivered = summary.state === 'TASK_STATE_COMPLETED' &&
|
|
728
|
+
(summary.assetIds.length > 0 || summary.mediaUrls.length > 0);
|
|
729
|
+
let editorUrl = null;
|
|
730
|
+
if (delivered) {
|
|
731
|
+
// Delivered by a turn a live card follows: the card shows them, so a
|
|
732
|
+
// get_wixel_asset_result for these ids is a duplicate.
|
|
733
|
+
const record = liveCards.get(task.contextId);
|
|
734
|
+
if (record?.tasks.has(task.id)) {
|
|
735
|
+
[
|
|
736
|
+
...summary.assetIds.slice(0, MAX_RESULT_ITEMS),
|
|
737
|
+
...summary.mediaUrls.slice(0, MAX_RESULT_ITEMS)
|
|
738
|
+
].forEach((item) => record.shownAssets.add(item));
|
|
739
|
+
}
|
|
740
|
+
// The session is bound to its Wixel project, which is the editor
|
|
741
|
+
// deep-link target.
|
|
742
|
+
try {
|
|
743
|
+
const projectId = sessionId
|
|
744
|
+
? await getWixelSessionProjectId({ ...base, sessionId })
|
|
745
|
+
: null;
|
|
746
|
+
if (projectId)
|
|
747
|
+
editorUrl = buildWixelEditorUrl(metaSiteId, projectId);
|
|
748
|
+
}
|
|
749
|
+
catch (projectError) {
|
|
750
|
+
console.warn('[Wixel] editor link resolution failed:', projectError.message);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// A question worth surfacing: on a working turn only a parked ask_user
|
|
754
|
+
// counts (a question already handled — policy fallback or answered in the
|
|
755
|
+
// Wixel UI — must not resurface while the turn keeps generating); on a
|
|
756
|
+
// completed turn without a delivery, the question the reply restates.
|
|
757
|
+
let pendingQuestion = null;
|
|
758
|
+
let turnInProgress = false;
|
|
759
|
+
if (sessionId && !failed && !delivered) {
|
|
760
|
+
try {
|
|
761
|
+
if (summary.done) {
|
|
762
|
+
pendingQuestion = await getWixelPendingQuestion({
|
|
763
|
+
...base,
|
|
764
|
+
sessionId,
|
|
765
|
+
turnId: summary.turnId
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
const activity = await getWixelSessionActivity({ ...base, sessionId });
|
|
770
|
+
pendingQuestion = activity.parkedQuestion;
|
|
771
|
+
turnInProgress = activity.turnInProgress;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
catch (questionError) {
|
|
775
|
+
console.warn('[Wixel] pending question fetch failed:', questionError.message);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// Not yet picked up, or between steps: still the turn's own work.
|
|
779
|
+
if (!summary.done && !pendingQuestion)
|
|
780
|
+
turnInProgress = true;
|
|
781
|
+
// The widget's answer to a question opens its own turn when the question's
|
|
782
|
+
// turn had already ended; the agent only knows the earlier task.
|
|
783
|
+
const latest = liveCards.get(task.contextId)?.lastSentTask ?? null;
|
|
784
|
+
const newerTaskId = summary.done && latest && latest !== task.id ? latest : null;
|
|
785
|
+
const lines = [];
|
|
786
|
+
if (failed) {
|
|
787
|
+
lines.push(`The turn ended without a result (${summary.state})${summary.replyText ? `: ${summary.replyText}` : '.'}`);
|
|
788
|
+
}
|
|
789
|
+
else if (summary.done) {
|
|
790
|
+
lines.push(summary.replyText
|
|
791
|
+
? `Wixel replied: ${summary.replyText}`
|
|
792
|
+
: 'The turn is done.');
|
|
793
|
+
}
|
|
794
|
+
else if (pendingQuestion) {
|
|
795
|
+
lines.push('The turn is parked on an interactive question awaiting the user.');
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
lines.push('Wixel is actively working on this turn.');
|
|
799
|
+
}
|
|
800
|
+
if (delivered) {
|
|
801
|
+
if (editorUrl)
|
|
802
|
+
lines.push(`Edit in Wixel: ${editorUrl}`);
|
|
803
|
+
lines.push(`<agent_instruction>The asset is READY — this reply is the final deliverable. You MUST now call get_wixel_asset_result with EXACTLY these arguments: ${ids}, assetIds: ${JSON.stringify(summary.assetIds)}, mediaUrls: ${JSON.stringify(summary.mediaUrls)}, prompt: <the user's original request>. In widget-capable clients it renders the result card (preview + edit/download actions); otherwise present the reply as text${editorUrl ? ' and ALWAYS include the Edit in Wixel link' : ''}. Do NOT call get_wixel_pending_question now — if the reply offers optional follow-ups (restyle, edits), mention them as text.</agent_instruction>`);
|
|
804
|
+
}
|
|
805
|
+
else if (pendingQuestion) {
|
|
806
|
+
lines.push(`IMPORTANT: the agent is asking the user an interactive question (${pendingQuestion.questions
|
|
807
|
+
.map((question) => question.kind)
|
|
808
|
+
.join(', ')}; questionId: ${pendingQuestion.toolCallId}). If you have NOT yet displayed this questionId, you MUST now call get_wixel_pending_question (${ids}, questionId: "${pendingQuestion.toolCallId}") and follow its response instruction BEFORE responding to the user — do not present the question yourself. If you already displayed this questionId, the user simply hasn't answered it yet — do NOT end your turn (stopping breaks the flow): keep polling get_wixel_task (waitSeconds: 25) without re-calling get_wixel_pending_question.`);
|
|
809
|
+
}
|
|
810
|
+
else if (newerTaskId) {
|
|
811
|
+
lines.push(`<agent_instruction>A newer turn was sent in this conversation since (taskId: "${newerTaskId}") — the user's answer from the widget. Poll get_wixel_task (taskId: "${newerTaskId}", metaSiteId: "${metaSiteId}", waitSeconds: 25) instead.</agent_instruction>`);
|
|
812
|
+
}
|
|
813
|
+
else if (turnInProgress) {
|
|
814
|
+
// A working turn must not read as "waiting on the user" — the caller's
|
|
815
|
+
// next step is to keep polling, nothing else.
|
|
816
|
+
lines.push(`<agent_instruction>Wixel is actively generating — the user does NOT need to do anything. Do NOT end your turn and do NOT ask the user for input: call get_wixel_task again (taskId: "${taskId}", metaSiteId: "${metaSiteId}", waitSeconds: 25) and repeat until it reports the turn done, however many polls it takes.</agent_instruction>`);
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
isError: false,
|
|
820
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
821
|
+
structuredContent: {
|
|
822
|
+
success: true,
|
|
823
|
+
state: summary.state,
|
|
824
|
+
done: summary.done,
|
|
825
|
+
replyText: summary.replyText,
|
|
826
|
+
assetIds: summary.assetIds,
|
|
827
|
+
mediaUrls: summary.mediaUrls,
|
|
828
|
+
contextId,
|
|
829
|
+
taskId,
|
|
830
|
+
hasPendingQuestion: !!pendingQuestion,
|
|
831
|
+
pendingQuestionId: pendingQuestion?.toolCallId ?? null,
|
|
832
|
+
turnInProgress,
|
|
833
|
+
editorUrl,
|
|
834
|
+
newerTaskId
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
941
838
|
export function addGetWixelStylePresetsTool(server, authStrategy, options = {}) {
|
|
942
839
|
const { getSiteAuthHeaders } = authStrategy;
|
|
943
840
|
registerAppTool(server, 'get_wixel_style_presets', {
|
|
@@ -995,13 +892,11 @@ export function addGetWixelStylePresetsTool(server, authStrategy, options = {})
|
|
|
995
892
|
}
|
|
996
893
|
// Widget-internal (not exposed to the model). The result card renders the
|
|
997
894
|
// moment the request is sent and drives the rest of the flow itself: it polls
|
|
998
|
-
// this for the answer's
|
|
999
|
-
// inline, the "done" assets, or "failed".
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
// only decides whether the turn is parked on a question, because the agent
|
|
1004
|
-
// emits text mid-turn and that log cannot tell it from the final reply.
|
|
895
|
+
// this for the answer's task and gets back "generating", a "question" to show
|
|
896
|
+
// inline, the "done" assets, or "failed". "Done" is the task's terminal state
|
|
897
|
+
// — the same signal the agent would have waited on with get_wixel_task; the
|
|
898
|
+
// entries log only decides whether the turn is parked on a question, because
|
|
899
|
+
// a parked turn's task stays WORKING.
|
|
1005
900
|
// `ui.visibility` is what MCP Apps hosts read; ChatGPT reads
|
|
1006
901
|
// `openai/visibility` instead, so both are set or its model would see the tool.
|
|
1007
902
|
const WIDGET_ONLY_TOOL_META = {
|
|
@@ -1015,17 +910,12 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1015
910
|
title: 'Get Wixel turn result',
|
|
1016
911
|
description: 'Widget-internal: the live result card polls this for the outcome of a turn (generating, a question to answer, the delivered assets, or a failure). Not needed for normal conversation flows.',
|
|
1017
912
|
inputSchema: {
|
|
1018
|
-
|
|
1019
|
-
metaSiteId:
|
|
1020
|
-
.string()
|
|
1021
|
-
.describe('Meta site ID from start_wixel_session'),
|
|
1022
|
-
turnId: z
|
|
1023
|
-
.string()
|
|
1024
|
-
.describe('The turn to report on (returned by send_wixel_message)'),
|
|
913
|
+
taskId: taskIdInput,
|
|
914
|
+
metaSiteId: metaSiteIdInput,
|
|
1025
915
|
live: z
|
|
1026
916
|
.boolean()
|
|
1027
917
|
.optional()
|
|
1028
|
-
.describe('Set by the live result card: marks the
|
|
918
|
+
.describe('Set by the live result card: marks the task as followed by the card, so the agent is told to stand back for it.')
|
|
1029
919
|
},
|
|
1030
920
|
annotations: {
|
|
1031
921
|
readOnlyHint: true,
|
|
@@ -1034,53 +924,46 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1034
924
|
},
|
|
1035
925
|
_meta: WIDGET_ONLY_TOOL_META
|
|
1036
926
|
}, async (args, extra) => {
|
|
1037
|
-
setBi(extra, {
|
|
1038
|
-
siteId: args.metaSiteId,
|
|
1039
|
-
conversationId: args.handle
|
|
1040
|
-
});
|
|
1041
|
-
// A turn the live card follows — including the turns its inline
|
|
1042
|
-
// answers open, which get_wixel_asset_result never saw.
|
|
1043
|
-
if (args.live) {
|
|
1044
|
-
const record = liveCards.forHandle(args.handle);
|
|
1045
|
-
if (!record.turns.has(args.turnId)) {
|
|
1046
|
-
record.turns.set(args.turnId, Date.now());
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
927
|
+
setBi(extra, { siteId: args.metaSiteId });
|
|
1049
928
|
const authHeaders = await getSiteAuthHeaders(args.metaSiteId);
|
|
1050
929
|
const base = {
|
|
1051
930
|
authHeaders,
|
|
1052
931
|
conductorOverride: options.conductorOverride
|
|
1053
932
|
};
|
|
1054
933
|
try {
|
|
1055
|
-
const
|
|
934
|
+
const task = await getSiteTask({
|
|
1056
935
|
...base,
|
|
1057
|
-
|
|
1058
|
-
metaSiteId: args.metaSiteId
|
|
1059
|
-
|
|
936
|
+
taskId: args.taskId,
|
|
937
|
+
metaSiteId: args.metaSiteId
|
|
938
|
+
});
|
|
939
|
+
const summary = summarizeTask(task);
|
|
940
|
+
const { sessionId } = summary;
|
|
941
|
+
setBi(extra, {
|
|
942
|
+
siteId: args.metaSiteId,
|
|
943
|
+
...(sessionId ? { conversationId: sessionId } : {})
|
|
1060
944
|
});
|
|
1061
|
-
|
|
1062
|
-
|
|
945
|
+
// A task the live card follows — including the tasks its inline
|
|
946
|
+
// answers open, which get_wixel_asset_result never saw.
|
|
947
|
+
if (args.live) {
|
|
948
|
+
const record = liveCards.forContext(task.contextId);
|
|
949
|
+
if (!record.tasks.has(task.id)) {
|
|
950
|
+
record.tasks.set(task.id, Date.now());
|
|
951
|
+
}
|
|
1063
952
|
}
|
|
1064
|
-
const terminal = (inbox.events ?? []).find((event) => event.turnId === args.turnId &&
|
|
1065
|
-
(event.type === 'reply' || event.type === 'failed'));
|
|
1066
953
|
let status = 'generating';
|
|
1067
954
|
let assets = [];
|
|
1068
955
|
let editorUrl = null;
|
|
1069
956
|
let pendingQuestion = null;
|
|
1070
|
-
const replyText =
|
|
1071
|
-
if (
|
|
957
|
+
const replyText = summary.done ? summary.replyText : null;
|
|
958
|
+
if (summary.done && summary.state !== 'TASK_STATE_COMPLETED') {
|
|
1072
959
|
status = 'failed';
|
|
1073
960
|
}
|
|
1074
|
-
else if (
|
|
961
|
+
else if (summary.done) {
|
|
1075
962
|
// Capped like the resolver's input, so what gets recorded as
|
|
1076
963
|
// shown below is exactly what was considered for rendering.
|
|
1077
|
-
const assetIds =
|
|
1078
|
-
const mediaUrls =
|
|
1079
|
-
|
|
1080
|
-
// an animation plan links the source image and asks for approval.
|
|
1081
|
-
const delivers = assetIds.length > 0 ||
|
|
1082
|
-
(mediaUrls.length > 0 && !isQuestionReply(terminal.text ?? ''));
|
|
1083
|
-
if (delivers) {
|
|
964
|
+
const assetIds = summary.assetIds.slice(0, MAX_RESULT_ITEMS);
|
|
965
|
+
const mediaUrls = summary.mediaUrls.slice(0, MAX_RESULT_ITEMS);
|
|
966
|
+
if (assetIds.length || mediaUrls.length) {
|
|
1084
967
|
status = 'done';
|
|
1085
968
|
// Only the live card displays what it resolves. The standalone
|
|
1086
969
|
// question widget polls for the status alone: resolving for it
|
|
@@ -1089,7 +972,7 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1089
972
|
if (args.live) {
|
|
1090
973
|
({ assets, editorUrl } = await resolveWixelAssetResults({
|
|
1091
974
|
...base,
|
|
1092
|
-
|
|
975
|
+
sessionId,
|
|
1093
976
|
metaSiteId: args.metaSiteId,
|
|
1094
977
|
assetIds,
|
|
1095
978
|
mediaUrls
|
|
@@ -1098,7 +981,7 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1098
981
|
// resolution that came back empty (transient lookup failure)
|
|
1099
982
|
// leaves the result for the agent's get_wixel_asset_result.
|
|
1100
983
|
if (assets.length) {
|
|
1101
|
-
const record = liveCards.
|
|
984
|
+
const record = liveCards.forContext(task.contextId);
|
|
1102
985
|
[...assetIds, ...mediaUrls].forEach((item) => record.shownAssets.add(item));
|
|
1103
986
|
}
|
|
1104
987
|
}
|
|
@@ -1107,19 +990,22 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1107
990
|
// A reply without a delivery is either a question restated as
|
|
1108
991
|
// text (its structured payload is still pending) or a plain
|
|
1109
992
|
// message — the card shows it with a reply box.
|
|
1110
|
-
pendingQuestion =
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
993
|
+
pendingQuestion = sessionId
|
|
994
|
+
? await getWixelPendingQuestion({
|
|
995
|
+
...base,
|
|
996
|
+
sessionId,
|
|
997
|
+
turnId: summary.turnId
|
|
998
|
+
})
|
|
999
|
+
: null;
|
|
1114
1000
|
status = pendingQuestion ? 'question' : 'done';
|
|
1115
1001
|
}
|
|
1116
1002
|
}
|
|
1117
|
-
else {
|
|
1118
|
-
//
|
|
1119
|
-
//
|
|
1003
|
+
else if (sessionId) {
|
|
1004
|
+
// Still working: parked on a question (custom_widget asks park the
|
|
1005
|
+
// turn without ending it) or generating.
|
|
1120
1006
|
const activity = await getWixelSessionActivity({
|
|
1121
1007
|
...base,
|
|
1122
|
-
sessionId
|
|
1008
|
+
sessionId
|
|
1123
1009
|
});
|
|
1124
1010
|
pendingQuestion = activity.parkedQuestion;
|
|
1125
1011
|
if (pendingQuestion)
|
|
@@ -1128,18 +1014,20 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1128
1014
|
console.log('[WixelResponseDump]', JSON.stringify({
|
|
1129
1015
|
tool: 'get_wixel_turn_result',
|
|
1130
1016
|
args,
|
|
1017
|
+
state: summary.state,
|
|
1131
1018
|
status,
|
|
1132
1019
|
assets,
|
|
1133
1020
|
editorUrl,
|
|
1134
1021
|
pendingQuestionId: pendingQuestion?.toolCallId ?? null
|
|
1135
1022
|
}).slice(0, 30000));
|
|
1136
1023
|
if (args.live && (status === 'done' || status === 'failed')) {
|
|
1137
|
-
liveCards.
|
|
1024
|
+
liveCards.forContext(task.contextId).finishedTasks.add(task.id);
|
|
1138
1025
|
}
|
|
1139
1026
|
const structuredContent = {
|
|
1140
1027
|
success: true,
|
|
1141
1028
|
status,
|
|
1142
|
-
|
|
1029
|
+
taskId: task.id,
|
|
1030
|
+
contextId: task.contextId,
|
|
1143
1031
|
assets,
|
|
1144
1032
|
editorUrl,
|
|
1145
1033
|
pendingQuestion,
|
|
@@ -1158,6 +1046,9 @@ export function addGetWixelTurnResultTool(server, authStrategy, options = {}) {
|
|
|
1158
1046
|
};
|
|
1159
1047
|
}
|
|
1160
1048
|
catch (error) {
|
|
1049
|
+
if (error instanceof WixelA2AError) {
|
|
1050
|
+
return a2aErrorResult('Could not read the Wixel task', error);
|
|
1051
|
+
}
|
|
1161
1052
|
rethrowIfUnauthorized(error);
|
|
1162
1053
|
console.error('❌ Error getting Wixel turn result:', error);
|
|
1163
1054
|
const errorMessage = error.message;
|
|
@@ -1178,7 +1069,7 @@ async function resolveWixelAssetResults(args) {
|
|
|
1178
1069
|
const { authHeaders } = args;
|
|
1179
1070
|
const options = { conductorOverride: args.conductorOverride };
|
|
1180
1071
|
// The model-facing schema caps these, but the live path feeds the resolver
|
|
1181
|
-
// straight from a
|
|
1072
|
+
// straight from a task's artifacts — cap here too, or one oversized reply
|
|
1182
1073
|
// fans out into an unbounded number of authenticated lookups.
|
|
1183
1074
|
const cappedAssetIds = (args.assetIds ?? []).slice(0, MAX_RESULT_ITEMS);
|
|
1184
1075
|
const cappedMediaUrls = (args.mediaUrls ?? []).slice(0, MAX_RESULT_ITEMS);
|
|
@@ -1187,11 +1078,13 @@ async function resolveWixelAssetResults(args) {
|
|
|
1187
1078
|
// are perfectly renderable.
|
|
1188
1079
|
let projectId = null;
|
|
1189
1080
|
try {
|
|
1190
|
-
projectId =
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1081
|
+
projectId = args.sessionId
|
|
1082
|
+
? await getWixelSessionProjectId({
|
|
1083
|
+
authHeaders,
|
|
1084
|
+
conductorOverride: options.conductorOverride,
|
|
1085
|
+
sessionId: args.sessionId
|
|
1086
|
+
})
|
|
1087
|
+
: null;
|
|
1195
1088
|
}
|
|
1196
1089
|
catch (projectError) {
|
|
1197
1090
|
console.warn('[Wixel] editor link resolution failed:', projectError.message);
|
|
@@ -1365,34 +1258,32 @@ export function addGetWixelAssetResultTool(server, authStrategy, options = {}) {
|
|
|
1365
1258
|
registerAppTool(server, 'get_wixel_asset_result', {
|
|
1366
1259
|
title: 'Get Wixel asset result',
|
|
1367
1260
|
description: `Render the result of a Wixel generation.
|
|
1368
|
-
In widget-capable clients call it RIGHT AFTER send_wixel_message with the returned
|
|
1369
|
-
Otherwise (or when
|
|
1261
|
+
In widget-capable clients call it RIGHT AFTER send_wixel_message with the returned contextId and taskId: it renders a live result card that shows generation progress, asks the user any questions Wixel has, and displays the final asset — no polling needed.
|
|
1262
|
+
Otherwise (or when get_wixel_task already reported the delivered assets) pass the assetIds and mediaUrls EXACTLY as get_wixel_task returned them (never invent ids — a taskId is NOT an asset id) and present the returned asset links as text, always including the Edit in Wixel link.`,
|
|
1370
1263
|
inputSchema: {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
.describe('Meta site ID from start_wixel_session'),
|
|
1264
|
+
contextId: contextIdInput,
|
|
1265
|
+
taskId: taskIdInput,
|
|
1266
|
+
metaSiteId: metaSiteIdInput,
|
|
1375
1267
|
// Bounded: one reply delivers a handful of assets, and each entry costs
|
|
1376
1268
|
// an authenticated lookup or a redirect chase.
|
|
1377
1269
|
assetIds: z
|
|
1378
1270
|
.array(z.string().min(1).max(200))
|
|
1379
1271
|
.max(MAX_RESULT_ITEMS)
|
|
1380
1272
|
.optional()
|
|
1381
|
-
.describe('Asset ids
|
|
1273
|
+
.describe('Asset ids the turn delivered — copy from get_wixel_task assetIds'),
|
|
1382
1274
|
mediaUrls: z
|
|
1383
1275
|
.array(z.string().max(2048))
|
|
1384
1276
|
.max(MAX_RESULT_ITEMS)
|
|
1385
1277
|
.optional()
|
|
1386
|
-
.describe('Media URLs
|
|
1278
|
+
.describe('Media URLs the turn delivered — copy from get_wixel_task mediaUrls'),
|
|
1279
|
+
// Required: besides heading the card, it is how the shared widget
|
|
1280
|
+
// tells this tool's invocation from get_wixel_pending_question's —
|
|
1281
|
+
// both carry the same conversation ids.
|
|
1387
1282
|
prompt: z
|
|
1388
1283
|
.string()
|
|
1389
|
-
.
|
|
1390
|
-
.
|
|
1391
|
-
.describe("The user's original generation request, shown above the preview")
|
|
1392
|
-
turnId: z
|
|
1393
|
-
.string()
|
|
1394
|
-
.optional()
|
|
1395
|
-
.describe('The turn returned by send_wixel_message. Widget clients: pass this (without assetIds/mediaUrls) to render the live card while Wixel generates.')
|
|
1284
|
+
.min(1)
|
|
1285
|
+
.max(WIXEL_MESSAGE_MAX_LENGTH)
|
|
1286
|
+
.describe("The user's original generation request, shown above the preview")
|
|
1396
1287
|
},
|
|
1397
1288
|
annotations: {
|
|
1398
1289
|
readOnlyHint: true,
|
|
@@ -1401,26 +1292,29 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1401
1292
|
},
|
|
1402
1293
|
_meta: QUESTION_TOOL_META
|
|
1403
1294
|
}, async (args, extra) => {
|
|
1404
|
-
setBi(extra, {
|
|
1405
|
-
siteId: args.metaSiteId,
|
|
1406
|
-
conversationId: args.handle
|
|
1407
|
-
});
|
|
1295
|
+
setBi(extra, { siteId: args.metaSiteId });
|
|
1408
1296
|
const authHeaders = await getSiteAuthHeaders(args.metaSiteId);
|
|
1297
|
+
const base = {
|
|
1298
|
+
authHeaders,
|
|
1299
|
+
conductorOverride: options.conductorOverride
|
|
1300
|
+
};
|
|
1409
1301
|
try {
|
|
1302
|
+
// The card sends its answers into contextId, so it must be the
|
|
1303
|
+
// task's own; the task also names the session behind the editor link.
|
|
1304
|
+
const task = await getSiteTask({
|
|
1305
|
+
...base,
|
|
1306
|
+
taskId: args.taskId,
|
|
1307
|
+
metaSiteId: args.metaSiteId
|
|
1308
|
+
});
|
|
1309
|
+
if (task.contextId !== args.contextId)
|
|
1310
|
+
return contextMismatchResult();
|
|
1311
|
+
const { sessionId } = summarizeTask(task);
|
|
1312
|
+
setBi(extra, {
|
|
1313
|
+
siteId: args.metaSiteId,
|
|
1314
|
+
...(sessionId ? { conversationId: sessionId } : {})
|
|
1315
|
+
});
|
|
1410
1316
|
if (!args.assetIds?.length && !args.mediaUrls?.length) {
|
|
1411
|
-
// Live mode: nothing to resolve yet — the widget follows the
|
|
1412
|
-
if (!args.turnId) {
|
|
1413
|
-
return {
|
|
1414
|
-
isError: true,
|
|
1415
|
-
content: [
|
|
1416
|
-
{
|
|
1417
|
-
type: 'text',
|
|
1418
|
-
text: 'Pass the turnId from send_wixel_message (widget clients) or the assetIds/mediaUrls a reply delivered.'
|
|
1419
|
-
}
|
|
1420
|
-
],
|
|
1421
|
-
structuredContent: { success: false, error: 'missing turnId' }
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1317
|
+
// Live mode: nothing to resolve yet — the widget follows the task.
|
|
1424
1318
|
if (options.supportsWidgets?.() !== true) {
|
|
1425
1319
|
return {
|
|
1426
1320
|
isError: false,
|
|
@@ -1428,19 +1322,19 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1428
1322
|
{
|
|
1429
1323
|
type: 'text',
|
|
1430
1324
|
text: `This client cannot render the live card.
|
|
1431
|
-
<agent_instruction>Poll
|
|
1325
|
+
<agent_instruction>Poll get_wixel_task (taskId: "${args.taskId}", metaSiteId: "${args.metaSiteId}", waitSeconds: 25) until it reports the turn done, then follow that response's instruction.</agent_instruction>`
|
|
1432
1326
|
}
|
|
1433
1327
|
],
|
|
1434
1328
|
structuredContent: {
|
|
1435
1329
|
success: false,
|
|
1436
1330
|
pending: true,
|
|
1437
|
-
|
|
1331
|
+
taskId: args.taskId
|
|
1438
1332
|
}
|
|
1439
1333
|
};
|
|
1440
1334
|
}
|
|
1441
|
-
const record = liveCards.
|
|
1442
|
-
if (record.
|
|
1443
|
-
// Already rendered for this
|
|
1335
|
+
const record = liveCards.forContext(task.contextId);
|
|
1336
|
+
if (record.tasks.has(task.id)) {
|
|
1337
|
+
// Already rendered for this task — a second card would show the
|
|
1444
1338
|
// same generation twice.
|
|
1445
1339
|
console.log('[WixelResponseDump]', JSON.stringify({
|
|
1446
1340
|
tool: 'get_wixel_asset_result',
|
|
@@ -1452,7 +1346,7 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1452
1346
|
success: true,
|
|
1453
1347
|
pending: true,
|
|
1454
1348
|
duplicate: true,
|
|
1455
|
-
|
|
1349
|
+
taskId: args.taskId,
|
|
1456
1350
|
assets: [],
|
|
1457
1351
|
editorUrl: null,
|
|
1458
1352
|
prompt: args.prompt ?? null
|
|
@@ -1463,7 +1357,7 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1463
1357
|
{
|
|
1464
1358
|
type: 'text',
|
|
1465
1359
|
text: `The live result card for this turn is already displayed.
|
|
1466
|
-
<agent_instruction>Do not call any Wixel tool again for this generation.
|
|
1360
|
+
<agent_instruction>Do not call any Wixel tool again for this generation. ${END_TURN_SILENTLY}</agent_instruction>`
|
|
1467
1361
|
},
|
|
1468
1362
|
{
|
|
1469
1363
|
type: 'text',
|
|
@@ -1473,12 +1367,12 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1473
1367
|
structuredContent: duplicateContent
|
|
1474
1368
|
};
|
|
1475
1369
|
}
|
|
1476
|
-
record.
|
|
1370
|
+
record.tasks.set(task.id, Date.now());
|
|
1477
1371
|
console.log('[WixelResponseDump]', JSON.stringify({ tool: 'get_wixel_asset_result', args, live: true }));
|
|
1478
1372
|
const liveContent = {
|
|
1479
1373
|
success: true,
|
|
1480
1374
|
pending: true,
|
|
1481
|
-
|
|
1375
|
+
taskId: args.taskId,
|
|
1482
1376
|
assets: [],
|
|
1483
1377
|
editorUrl: null,
|
|
1484
1378
|
prompt: args.prompt ?? null
|
|
@@ -1489,7 +1383,7 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1489
1383
|
{
|
|
1490
1384
|
type: 'text',
|
|
1491
1385
|
text: `The live result card is displayed to the user. It shows generation progress, lets the user answer any question Wixel asks, and displays the final asset with edit/download actions — all inside the card.
|
|
1492
|
-
<agent_instruction>Do NOT poll
|
|
1386
|
+
<agent_instruction>Do NOT poll get_wixel_task and do NOT call get_wixel_pending_question — the card handles the rest of this generation itself. ${END_TURN_SILENTLY}</agent_instruction>`
|
|
1493
1387
|
},
|
|
1494
1388
|
{ type: 'text', text: JSON.stringify(liveContent) }
|
|
1495
1389
|
],
|
|
@@ -1497,10 +1391,10 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1497
1391
|
_meta: widgetResultMeta(server)
|
|
1498
1392
|
};
|
|
1499
1393
|
}
|
|
1500
|
-
// A live card already displays these assets (
|
|
1501
|
-
// get_wixel_turn_result recorded them for a live
|
|
1394
|
+
// A live card already displays these assets (get_wixel_task or
|
|
1395
|
+
// get_wixel_turn_result recorded them for a live task) — rendering
|
|
1502
1396
|
// them again would put the result in the chat twice.
|
|
1503
|
-
const shown = liveCards.get(
|
|
1397
|
+
const shown = liveCards.get(task.contextId)?.shownAssets;
|
|
1504
1398
|
const requested = [...(args.assetIds ?? []), ...(args.mediaUrls ?? [])];
|
|
1505
1399
|
if (options.supportsWidgets?.() === true &&
|
|
1506
1400
|
shown?.size &&
|
|
@@ -1523,7 +1417,7 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1523
1417
|
{
|
|
1524
1418
|
type: 'text',
|
|
1525
1419
|
text: `This result is already displayed by the live result card.
|
|
1526
|
-
<agent_instruction>Do NOT present the asset again (no links, no image).
|
|
1420
|
+
<agent_instruction>Do NOT present the asset again (no links, no image). ${END_TURN_SILENTLY}</agent_instruction>`
|
|
1527
1421
|
},
|
|
1528
1422
|
{ type: 'text', text: JSON.stringify(duplicateContent) }
|
|
1529
1423
|
],
|
|
@@ -1531,9 +1425,8 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1531
1425
|
};
|
|
1532
1426
|
}
|
|
1533
1427
|
const { assets, editorUrl } = await resolveWixelAssetResults({
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
handle: args.handle,
|
|
1428
|
+
...base,
|
|
1429
|
+
sessionId,
|
|
1537
1430
|
metaSiteId: args.metaSiteId,
|
|
1538
1431
|
assetIds: args.assetIds,
|
|
1539
1432
|
mediaUrls: args.mediaUrls
|
|
@@ -1589,6 +1482,9 @@ Otherwise (or when a list_wixel_events reply already delivered assets) pass the
|
|
|
1589
1482
|
};
|
|
1590
1483
|
}
|
|
1591
1484
|
catch (error) {
|
|
1485
|
+
if (error instanceof WixelA2AError) {
|
|
1486
|
+
return a2aErrorResult('Could not read the Wixel task', error);
|
|
1487
|
+
}
|
|
1592
1488
|
rethrowIfUnauthorized(error);
|
|
1593
1489
|
console.error('❌ Error resolving Wixel asset result:', error);
|
|
1594
1490
|
const errorMessage = error.message;
|
|
@@ -1605,9 +1501,8 @@ export function addWixelSessionTools(server, authStrategy, options = {}) {
|
|
|
1605
1501
|
...options,
|
|
1606
1502
|
liveCards: options.liveCards ?? createLiveCardRegistry()
|
|
1607
1503
|
};
|
|
1608
|
-
addStartWixelSessionTool(server, authStrategy, shared);
|
|
1609
1504
|
addSendWixelMessageTool(server, authStrategy, shared);
|
|
1610
|
-
|
|
1505
|
+
addGetWixelTaskTool(server, authStrategy, shared);
|
|
1611
1506
|
addGetWixelPendingQuestionTool(server, authStrategy, shared);
|
|
1612
1507
|
addGetWixelAssetResultTool(server, authStrategy, shared);
|
|
1613
1508
|
addGetWixelStylePresetsTool(server, authStrategy, shared);
|