beplus-mcp 0.28.0 → 0.30.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 +37 -5
- package/dist/index.js +688 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22141,7 +22141,9 @@ var IMAGE_MODELS = [
|
|
|
22141
22141
|
"midjourney"
|
|
22142
22142
|
];
|
|
22143
22143
|
var SEEDREAM_LITE_SIZES = ["2K", "3K", "4K"];
|
|
22144
|
-
var SEEDREAM_PRO_SIZES = ["1K", "2K"];
|
|
22144
|
+
var SEEDREAM_PRO_SIZES = ["1K", "1.5K", "2K"];
|
|
22145
|
+
var SEEDREAM_PRO_PIXELS = { min: 921600, max: 4624220 };
|
|
22146
|
+
var SEEDREAM_PRO_MAX_IMAGES = 4;
|
|
22145
22147
|
var VIDEO_MODELS = [
|
|
22146
22148
|
"bytedance/seedance-2.5",
|
|
22147
22149
|
"bytedance/seedance-2.0",
|
|
@@ -22263,17 +22265,25 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22263
22265
|
// que o modelo aceita (Seedream/nano-banana não têm limite publicado).
|
|
22264
22266
|
prompt: external_exports.string().min(1).describe("Descri\xE7\xE3o da imagem desejada."),
|
|
22265
22267
|
model: external_exports.enum(IMAGE_MODELS).default(DEFAULTS.imageModel).describe(
|
|
22266
|
-
"nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
|
|
22268
|
+
"nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K (1K/1.5K/2K, propor\xE7\xE3o, tamanho livre e at\xE9 4 imagens por pedido) \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
|
|
22269
|
+
),
|
|
22270
|
+
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe(
|
|
22271
|
+
"Propor\xE7\xE3o. nano-banana: padr\xE3o 1:1. Seedream Pro: combina com size (1K/1.5K/2K) e vira LARGURAxALTURA no backend; sem ela o modelo escolhe o formato pelo prompt."
|
|
22267
22272
|
),
|
|
22268
|
-
aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe("Propor\xE7\xE3o (nano-banana). Padr\xE3o 1:1."),
|
|
22269
22273
|
resolution: external_exports.enum(IMAGE_RESOLUTIONS).optional().describe("Resolu\xE7\xE3o (nano-banana): 1K/2K/4K. Padr\xE3o 1K."),
|
|
22270
22274
|
reference_images: external_exports.array(external_exports.string().min(1)).max(16).optional().describe(
|
|
22271
22275
|
"Imagens de refer\xEAncia (image-to-image): URL p\xFAblica OU caminho de arquivo local (o MCP faz upload pro R2 automaticamente). At\xE9 14 (nano-banana) / 16 (gpt-image-1/2)."
|
|
22272
22276
|
),
|
|
22273
22277
|
google_search: external_exports.boolean().optional().describe("nano-banana-2: aterra a imagem em busca web do Google (fatos atuais). Padr\xE3o false."),
|
|
22274
22278
|
image_search: external_exports.boolean().optional().describe("nano-banana-2: usa busca de imagens do Google como refer\xEAncia visual. Padr\xE3o false."),
|
|
22275
|
-
size: external_exports.
|
|
22276
|
-
"
|
|
22279
|
+
size: external_exports.union([
|
|
22280
|
+
external_exports.enum(["1024x1024", "1536x1024", "1024x1536", "auto", "1K", "1.5K", "2K", "3K", "4K"]),
|
|
22281
|
+
external_exports.string().regex(/^\d{3,5}x\d{3,5}$/, "use LARGURAxALTURA, ex.: 2048x1152")
|
|
22282
|
+
]).optional().describe(
|
|
22283
|
+
"Tamanho. gpt-image-1/2: 1024x1024/1536x1024/1024x1536/auto. Seedream Lite: 2K/3K/4K (padr\xE3o 2K). Seedream Pro: 1K/1.5K/2K ou LARGURAxALTURA livre (\xE1rea entre 921.600 e 4.624.220 pixels, propor\xE7\xE3o de 1:16 a 16:1). O Pro N\xC3O faz 3K/4K."
|
|
22284
|
+
),
|
|
22285
|
+
num_images: external_exports.number().int().min(1).max(SEEDREAM_PRO_MAX_IMAGES).optional().describe(
|
|
22286
|
+
"Seedream 5.0 PRO apenas: quantas imagens gerar no mesmo pedido (1-4), em chamadas paralelas. Cada imagem \xE9 cobrada; as que falharem s\xE3o estornadas."
|
|
22277
22287
|
),
|
|
22278
22288
|
batch_images: external_exports.number().int().min(2).max(15).optional().describe(
|
|
22279
22289
|
"Seedream 5.0 LITE apenas: gera uma s\xE9rie de imagens relacionadas numa chamada (2-15). Cada imagem \xE9 cobrada. O Seedream Pro N\xC3O suporta \u2014 passar aqui devolve erro."
|
|
@@ -22314,7 +22324,19 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22314
22324
|
const isSeedream = isSeedreamLite || isSeedreamPro;
|
|
22315
22325
|
if (isSeedream) {
|
|
22316
22326
|
const allowed = isSeedreamPro ? SEEDREAM_PRO_SIZES : SEEDREAM_LITE_SIZES;
|
|
22317
|
-
|
|
22327
|
+
const free = args.size?.match(/^(\d+)x(\d+)$/);
|
|
22328
|
+
if (isSeedreamPro && free) {
|
|
22329
|
+
const w = Number(free[1]);
|
|
22330
|
+
const h = Number(free[2]);
|
|
22331
|
+
if (w * h < SEEDREAM_PRO_PIXELS.min || w * h > SEEDREAM_PRO_PIXELS.max) {
|
|
22332
|
+
return errorResult(
|
|
22333
|
+
`size ${args.size} d\xE1 ${w * h} pixels; o Pro aceita entre ${SEEDREAM_PRO_PIXELS.min} e ${SEEDREAM_PRO_PIXELS.max} (ex.: 1280x720 a 2048x2048).`
|
|
22334
|
+
);
|
|
22335
|
+
}
|
|
22336
|
+
if (w / h > 16 || h / w > 16) {
|
|
22337
|
+
return errorResult(`size ${args.size}: a propor\xE7\xE3o precisa ficar entre 1:16 e 16:1.`);
|
|
22338
|
+
}
|
|
22339
|
+
} else if (args.size && !allowed.includes(args.size)) {
|
|
22318
22340
|
return errorResult(
|
|
22319
22341
|
`${args.model} aceita size ${allowed.join("/")} (voc\xEA pediu ${args.size}). ` + (isSeedreamPro ? "O Pro n\xE3o faz 3K/4K \u2014 use o seedream-5.0-lite para resolu\xE7\xF5es maiores." : "O Lite n\xE3o faz 1K \u2014 use o seedream-5.0-pro se quiser a resolu\xE7\xE3o menor.")
|
|
22320
22342
|
);
|
|
@@ -22336,6 +22358,11 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22336
22358
|
} else if (args.batch_images) {
|
|
22337
22359
|
return errorResult("batch_images s\xF3 existe no bytedance/seedream-5.0-lite.");
|
|
22338
22360
|
}
|
|
22361
|
+
if (args.num_images && args.num_images > 1 && !isSeedreamPro) {
|
|
22362
|
+
return errorResult(
|
|
22363
|
+
"num_images s\xF3 existe no bytedance/seedream-5.0-pro. No seedream-5.0-lite use batch_images; nos outros modelos, chame generate_image mais vezes."
|
|
22364
|
+
);
|
|
22365
|
+
}
|
|
22339
22366
|
if (args.background === "transparent") {
|
|
22340
22367
|
if (args.model !== "openai/gpt-image-1") {
|
|
22341
22368
|
return errorResult(
|
|
@@ -22365,6 +22392,7 @@ function registerGenerationTools(server, client, cfg) {
|
|
|
22365
22392
|
// amigável exposto aqui.
|
|
22366
22393
|
sequential_image_generation: isSeedreamLite && args.batch_images ? "auto" : void 0,
|
|
22367
22394
|
max_images: isSeedreamLite && args.batch_images ? args.batch_images : void 0,
|
|
22395
|
+
num_images: isSeedreamPro && args.num_images && args.num_images > 1 ? args.num_images : void 0,
|
|
22368
22396
|
quality: isMidjourney ? args.mj_quality : args.quality,
|
|
22369
22397
|
background: args.background,
|
|
22370
22398
|
moderation: args.moderation,
|
|
@@ -23004,7 +23032,7 @@ function probeWebp(b) {
|
|
|
23004
23032
|
}
|
|
23005
23033
|
function mp4Info(b) {
|
|
23006
23034
|
let duration3;
|
|
23007
|
-
let
|
|
23035
|
+
let width2 = 0;
|
|
23008
23036
|
let height = 0;
|
|
23009
23037
|
let frames = 0;
|
|
23010
23038
|
let mediaDuration;
|
|
@@ -23046,8 +23074,8 @@ function mp4Info(b) {
|
|
|
23046
23074
|
const rotated = Math.abs(a) < 0.01 && Math.abs(d) < 0.01;
|
|
23047
23075
|
const tw = Math.round(rotated ? h : w);
|
|
23048
23076
|
const th = Math.round(rotated ? w : h);
|
|
23049
|
-
if (tw * th >
|
|
23050
|
-
|
|
23077
|
+
if (tw * th > width2 * height) {
|
|
23078
|
+
width2 = tw;
|
|
23051
23079
|
height = th;
|
|
23052
23080
|
}
|
|
23053
23081
|
}
|
|
@@ -23071,8 +23099,8 @@ function mp4Info(b) {
|
|
|
23071
23099
|
};
|
|
23072
23100
|
walk(0, b.length, 0);
|
|
23073
23101
|
const dur = duration3 ?? mediaDuration;
|
|
23074
|
-
const fps =
|
|
23075
|
-
return { width, height, duration: dur, fps };
|
|
23102
|
+
const fps = width2 && height && dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
|
|
23103
|
+
return { width: width2, height, duration: dur, fps };
|
|
23076
23104
|
}
|
|
23077
23105
|
function probeMp4(b) {
|
|
23078
23106
|
const info = mp4Info(b);
|
|
@@ -23100,9 +23128,9 @@ async function probeMedia(ref) {
|
|
|
23100
23128
|
}
|
|
23101
23129
|
if (!head.length) throw new ProbeError("Arquivo vazio.");
|
|
23102
23130
|
if (kind === "image") {
|
|
23103
|
-
const
|
|
23104
|
-
if (!
|
|
23105
|
-
return { kind, width:
|
|
23131
|
+
const dim2 = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
|
|
23132
|
+
if (!dim2) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
|
|
23133
|
+
return { kind, width: dim2.width, height: dim2.height };
|
|
23106
23134
|
}
|
|
23107
23135
|
let info = probeMp4(head);
|
|
23108
23136
|
if (!info) {
|
|
@@ -23186,8 +23214,8 @@ function probeMp3(b) {
|
|
|
23186
23214
|
function probeBuffer(b, mime) {
|
|
23187
23215
|
const round = (n) => Math.round(n * 1e3) / 1e3;
|
|
23188
23216
|
if (mime.startsWith("image/")) {
|
|
23189
|
-
const
|
|
23190
|
-
return
|
|
23217
|
+
const dim2 = probePng(b) ?? probeJpeg(b) ?? probeWebp(b) ?? probeGif(b);
|
|
23218
|
+
return dim2 ? { width: dim2.width, height: dim2.height } : null;
|
|
23191
23219
|
}
|
|
23192
23220
|
if (/^(video|audio)\/(mp4|quicktime|x-m4v|x-m4a|aac)$/.test(mime) || probeMp4Head(b)) {
|
|
23193
23221
|
const info = mp4Info(b);
|
|
@@ -23316,14 +23344,14 @@ function registerUpscaleTools(server, client, cfg) {
|
|
|
23316
23344
|
`"${variant}" n\xE3o \xE9 um modelo de ${isVideo ? "v\xEDdeo" : "imagem"}. Use um destes: ${allowed.join(", ")}.`
|
|
23317
23345
|
);
|
|
23318
23346
|
}
|
|
23319
|
-
let
|
|
23347
|
+
let width2 = args.source_width;
|
|
23320
23348
|
let height = args.source_height;
|
|
23321
23349
|
let duration3 = args.source_duration;
|
|
23322
23350
|
let fps = args.source_fps;
|
|
23323
|
-
if (!
|
|
23351
|
+
if (!width2 || !height || isVideo && !duration3) {
|
|
23324
23352
|
try {
|
|
23325
23353
|
const probed = await probeMedia(args.file);
|
|
23326
|
-
|
|
23354
|
+
width2 = width2 ?? probed.width;
|
|
23327
23355
|
height = height ?? probed.height;
|
|
23328
23356
|
duration3 = duration3 ?? probed.duration;
|
|
23329
23357
|
fps = fps ?? probed.fps;
|
|
@@ -23358,7 +23386,7 @@ function registerUpscaleTools(server, client, cfg) {
|
|
|
23358
23386
|
model: variant,
|
|
23359
23387
|
upscale_factor: factor,
|
|
23360
23388
|
prompt: args.prompt || label,
|
|
23361
|
-
source_width:
|
|
23389
|
+
source_width: width2,
|
|
23362
23390
|
source_height: height,
|
|
23363
23391
|
source_duration: duration3,
|
|
23364
23392
|
source_fps: fps,
|
|
@@ -23414,7 +23442,7 @@ ${footer}`);
|
|
|
23414
23442
|
}
|
|
23415
23443
|
const urls = outputUrls(status);
|
|
23416
23444
|
const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
|
|
23417
|
-
const saida =
|
|
23445
|
+
const saida = width2 && height ? ` \u2014 sa\xEDda ${width2 * factor} \xD7 ${height * factor}` : "";
|
|
23418
23446
|
const semAudio = isVideo ? "\nO \xE1udio n\xE3o foi mantido (o Topaz descarta a faixa)." : "";
|
|
23419
23447
|
return textResult(
|
|
23420
23448
|
`\u2705 ${isVideo ? "V\xEDdeo" : "Imagem"} melhorado em ${factor}x com ${variant}${saida}.
|
|
@@ -23671,6 +23699,8 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
23671
23699
|
mode: external_exports.string().optional(),
|
|
23672
23700
|
size: external_exports.string().optional(),
|
|
23673
23701
|
quality: external_exports.string().optional(),
|
|
23702
|
+
aspect_ratio: external_exports.string().optional().describe("Seedream Pro: a propor\xE7\xE3o muda os pixels e a faixa de pre\xE7o."),
|
|
23703
|
+
num_images: external_exports.number().int().min(1).optional().describe("Seedream Pro: imagens por pedido; cada uma \xE9 cobrada."),
|
|
23674
23704
|
// Upscale: o preço não sai de uma chave fixa — vem das medidas do
|
|
23675
23705
|
// arquivo (megapixel de saída na imagem, bloco de 10s no vídeo). Sem
|
|
23676
23706
|
// elas o backend responde pelo padrão de 1024²/10s, que não é o preço
|
|
@@ -23688,14 +23718,14 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
23688
23718
|
},
|
|
23689
23719
|
async (args) => {
|
|
23690
23720
|
try {
|
|
23691
|
-
let
|
|
23721
|
+
let width2 = args.source_width;
|
|
23692
23722
|
let height = args.source_height;
|
|
23693
23723
|
let duration3 = args.source_duration;
|
|
23694
23724
|
let fps = args.source_fps;
|
|
23695
|
-
if (args.file && (!
|
|
23725
|
+
if (args.file && (!width2 || !height)) {
|
|
23696
23726
|
try {
|
|
23697
23727
|
const probed = await probeMedia(args.file);
|
|
23698
|
-
|
|
23728
|
+
width2 = width2 ?? probed.width;
|
|
23699
23729
|
height = height ?? probed.height;
|
|
23700
23730
|
duration3 = duration3 ?? probed.duration;
|
|
23701
23731
|
fps = fps ?? probed.fps;
|
|
@@ -23712,9 +23742,11 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
|
|
|
23712
23742
|
mode: args.mode,
|
|
23713
23743
|
size: args.size,
|
|
23714
23744
|
quality: args.quality,
|
|
23745
|
+
aspect_ratio: args.aspect_ratio,
|
|
23746
|
+
num_images: args.num_images,
|
|
23715
23747
|
upscale_factor: args.upscale_factor,
|
|
23716
23748
|
model_variant: args.model_variant,
|
|
23717
|
-
source_width:
|
|
23749
|
+
source_width: width2,
|
|
23718
23750
|
source_height: height,
|
|
23719
23751
|
source_duration: duration3,
|
|
23720
23752
|
source_fps: fps
|
|
@@ -24397,11 +24429,14 @@ function isValidNode(key, v) {
|
|
|
24397
24429
|
function isValidEdge(key, v) {
|
|
24398
24430
|
return isRecord(v) && hasOnlyKeys(v, EDGE_KEYS) && v.id === key && typeof v.source === "string" && typeof v.sourcePort === "string" && typeof v.target === "string" && typeof v.targetPort === "string" && isJsonBag(v.data) && isStringOrNull(v.createdBy) && isFiniteNumber(v.createdAt);
|
|
24399
24431
|
}
|
|
24432
|
+
var MEASURED_KEY = "__measured";
|
|
24400
24433
|
function checkNodePath(path) {
|
|
24401
24434
|
const segments = path.split(".");
|
|
24402
24435
|
if (segments.some((s) => s.length === 0)) return `caminho inv\xE1lido "${path}"`;
|
|
24403
24436
|
if (segments.some((s) => FORBIDDEN_SEGMENTS.includes(s))) return `segmento proibido em "${path}"`;
|
|
24404
24437
|
const [root, ...rest] = segments;
|
|
24438
|
+
if (root === "data" && rest[0] === MEASURED_KEY)
|
|
24439
|
+
return `data.${MEASURED_KEY} \xE9 o tamanho medido pela tela do Canvas: s\xF3 o Canvas grava`;
|
|
24405
24440
|
if (root === "data" && rest.length >= 1) return null;
|
|
24406
24441
|
if (root === "position" && rest.length === 1 && ["x", "y"].includes(rest[0]) || root === "size" && rest.length === 1 && ["w", "h"].includes(rest[0])) return null;
|
|
24407
24442
|
return `s\xF3 d\xE1 para editar data.*, position.x/y e size.w/h (recebido "${path}")`;
|
|
@@ -24549,6 +24584,120 @@ function packEnvelopes(boardId3, ops, actorId, limit = MAX_OP_BYTES - 1024) {
|
|
|
24549
24584
|
return out;
|
|
24550
24585
|
}
|
|
24551
24586
|
|
|
24587
|
+
// src/canvas/layout.ts
|
|
24588
|
+
function readMeasured(node) {
|
|
24589
|
+
const m = node.data[MEASURED_KEY];
|
|
24590
|
+
if (!m || typeof m !== "object" || Array.isArray(m)) return null;
|
|
24591
|
+
const { w, h, at } = m;
|
|
24592
|
+
if (typeof w !== "number" || typeof h !== "number" || !Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null;
|
|
24593
|
+
return { w, h, at: typeof at === "string" ? at : null };
|
|
24594
|
+
}
|
|
24595
|
+
var SAME_WIDTH = 2;
|
|
24596
|
+
var WRAP_KINDS = /* @__PURE__ */ new Set(["prompt", "text", "block", "note", "label", "llm", "narration", "copy", "review", "shotlist"]);
|
|
24597
|
+
var SCALE_KINDS = /* @__PURE__ */ new Set(["image", "video", "media"]);
|
|
24598
|
+
var kindOf = (node) => typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : "";
|
|
24599
|
+
function heightAt(kind, baseW, baseH, newW) {
|
|
24600
|
+
if (Math.abs(newW - baseW) <= SAME_WIDTH) return baseH;
|
|
24601
|
+
if (SCALE_KINDS.has(kind) && newW > baseW) return Math.ceil(baseH * newW / baseW);
|
|
24602
|
+
if (WRAP_KINDS.has(kind) && newW < baseW) return Math.ceil(baseH * baseW / newW);
|
|
24603
|
+
return baseH;
|
|
24604
|
+
}
|
|
24605
|
+
function usedSize(node, floorW = node.size.w) {
|
|
24606
|
+
const kind = kindOf(node);
|
|
24607
|
+
const m = readMeasured(node);
|
|
24608
|
+
if (!m) {
|
|
24609
|
+
const h = Math.max(node.size.h, heightAt(kind, node.size.w, node.size.h, floorW));
|
|
24610
|
+
return { w: floorW, h, source: "gravado", at: null, estimated: h !== node.size.h };
|
|
24611
|
+
}
|
|
24612
|
+
const keepsWidth = Math.abs(floorW - node.size.w) <= SAME_WIDTH;
|
|
24613
|
+
return {
|
|
24614
|
+
w: keepsWidth ? Math.max(floorW, m.w) : floorW,
|
|
24615
|
+
h: Math.max(node.size.h, heightAt(kind, m.w, m.h, floorW)),
|
|
24616
|
+
source: "medido",
|
|
24617
|
+
at: m.at,
|
|
24618
|
+
estimated: Math.abs(floorW - m.w) > SAME_WIDTH
|
|
24619
|
+
};
|
|
24620
|
+
}
|
|
24621
|
+
function sizeLabel(node) {
|
|
24622
|
+
const u = usedSize(node);
|
|
24623
|
+
const base = `${Math.round(u.w)}\xD7${Math.round(u.h)} ${u.source}`;
|
|
24624
|
+
if (u.source === "medido" && u.estimated) return `${base} (medido em ${Math.round(readMeasured(node).w)} de largura: altura estimada)`;
|
|
24625
|
+
return base;
|
|
24626
|
+
}
|
|
24627
|
+
var DEFAULT_WIDTHS = { prompt: 1024, image: 560, video: 560, reference: 320, frame: 320, title: 1024 };
|
|
24628
|
+
var DEFAULT_GAP = 80;
|
|
24629
|
+
var FRAME_PORTS = /* @__PURE__ */ new Set(["frame"]);
|
|
24630
|
+
var FEEDS = /* @__PURE__ */ new Set(["prompt", "image", "video"]);
|
|
24631
|
+
var outOf = (doc, id) => Object.values(doc.edges).filter((e) => e.source === id);
|
|
24632
|
+
function roleOf(doc, node, asTitle) {
|
|
24633
|
+
if (asTitle) return "title";
|
|
24634
|
+
const kind = kindOf(node);
|
|
24635
|
+
if (kind === "prompt" || kind === "video") return kind;
|
|
24636
|
+
if (kind !== "media" && kind !== "image") return "other";
|
|
24637
|
+
const feedsFrame = outOf(doc, node.id).some((e) => FRAME_PORTS.has(e.targetPort) && doc.nodes[e.target] && kindOf(doc.nodes[e.target]) === "video");
|
|
24638
|
+
if (feedsFrame) return "frame";
|
|
24639
|
+
return kind === "media" ? "reference" : "image";
|
|
24640
|
+
}
|
|
24641
|
+
function hangsRight(doc, node) {
|
|
24642
|
+
const kind = kindOf(node);
|
|
24643
|
+
if (kind !== "media" && kind !== "image") return false;
|
|
24644
|
+
const out = outOf(doc, node.id);
|
|
24645
|
+
if (!out.length) return kind === "media";
|
|
24646
|
+
return out.some((e) => doc.nodes[e.target] && FEEDS.has(kindOf(doc.nodes[e.target])));
|
|
24647
|
+
}
|
|
24648
|
+
var overlap = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
|
24649
|
+
function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDTHS) {
|
|
24650
|
+
const placed = [];
|
|
24651
|
+
const floorOf = (node, role) => role === "other" ? node.size.w : widths[role];
|
|
24652
|
+
const place = (id, row, asTitle, x, y2, alignRight) => {
|
|
24653
|
+
const node = doc.nodes[id];
|
|
24654
|
+
const role = roleOf(doc, node, asTitle);
|
|
24655
|
+
const floorW = floorOf(node, role);
|
|
24656
|
+
const u = usedSize(node, floorW);
|
|
24657
|
+
const w = Math.ceil(u.w);
|
|
24658
|
+
const h = Math.ceil(u.h);
|
|
24659
|
+
const px = alignRight !== void 0 ? alignRight - w : x;
|
|
24660
|
+
const p = { id, row, role, x: Math.round(px), y: Math.round(y2), w, h, source: u.source, estimated: u.estimated, floorW };
|
|
24661
|
+
placed.push(p);
|
|
24662
|
+
return p;
|
|
24663
|
+
};
|
|
24664
|
+
let y = origin.y;
|
|
24665
|
+
rows.forEach((row, r) => {
|
|
24666
|
+
if (row.title) {
|
|
24667
|
+
const t = place(row.title, r, true, origin.x, y);
|
|
24668
|
+
y = t.y + t.h + Math.round(gap / 2);
|
|
24669
|
+
}
|
|
24670
|
+
const top = y;
|
|
24671
|
+
const leftW = Math.max(0, ...row.left.map((id) => Math.ceil(usedSize(doc.nodes[id], floorOf(doc.nodes[id], roleOf(doc, doc.nodes[id], false))).w)));
|
|
24672
|
+
let leftEnd = top;
|
|
24673
|
+
row.left.forEach((id, i) => {
|
|
24674
|
+
const right2 = hangsRight(doc, doc.nodes[id]);
|
|
24675
|
+
const p = place(id, r, false, origin.x, i === 0 ? top : leftEnd + gap, right2 ? origin.x + leftW : void 0);
|
|
24676
|
+
leftEnd = p.y + p.h;
|
|
24677
|
+
});
|
|
24678
|
+
const rightX = origin.x + (row.left.length ? leftW + gap : 0);
|
|
24679
|
+
let rightEnd = top;
|
|
24680
|
+
row.right.forEach((id, i) => {
|
|
24681
|
+
const p = place(id, r, false, rightX, i === 0 ? top : rightEnd + gap);
|
|
24682
|
+
rightEnd = p.y + p.h;
|
|
24683
|
+
});
|
|
24684
|
+
y = Math.max(leftEnd, rightEnd, top) + gap * 2;
|
|
24685
|
+
});
|
|
24686
|
+
const inner = [];
|
|
24687
|
+
for (let i = 0; i < placed.length; i++) {
|
|
24688
|
+
for (let j = i + 1; j < placed.length; j++) if (overlap(placed[i], placed[j])) inner.push([placed[i].id, placed[j].id]);
|
|
24689
|
+
}
|
|
24690
|
+
const ids = new Set(placed.map((p) => p.id));
|
|
24691
|
+
const others = Object.values(doc.nodes).filter((n) => !ids.has(n.id) && kindOf(n) !== "section").map((n) => ({ id: n.id, x: n.position.x, y: n.position.y, ...usedSize(n) }));
|
|
24692
|
+
const outer = [];
|
|
24693
|
+
for (const p of placed) for (const o of others) if (overlap(p, o)) outer.push([p.id, o.id]);
|
|
24694
|
+
const left = Math.min(origin.x, ...placed.map((p) => p.x));
|
|
24695
|
+
const right = Math.max(origin.x, ...placed.map((p) => p.x + p.w));
|
|
24696
|
+
const below = others.filter((o) => o.x < right && left < o.x + o.w).map((o) => o.y + o.h);
|
|
24697
|
+
const freeOrigin = { x: origin.x, y: below.length ? Math.max(origin.y, Math.ceil(Math.max(...below) + gap * 2)) : origin.y };
|
|
24698
|
+
return { placed, inner, outer, freeOrigin };
|
|
24699
|
+
}
|
|
24700
|
+
|
|
24552
24701
|
// src/canvas/summary.ts
|
|
24553
24702
|
var MAX_TEXT = 160;
|
|
24554
24703
|
var MAX_BLOCKS = 40;
|
|
@@ -24571,8 +24720,14 @@ var HEAVY_KEYS = /* @__PURE__ */ new Set([
|
|
|
24571
24720
|
"settings",
|
|
24572
24721
|
"enabledKeys",
|
|
24573
24722
|
"checks",
|
|
24574
|
-
"versions"
|
|
24723
|
+
"versions",
|
|
24724
|
+
MEASURED_KEY
|
|
24575
24725
|
]);
|
|
24726
|
+
function visibleData(data) {
|
|
24727
|
+
if (!Object.hasOwn(data, MEASURED_KEY)) return data;
|
|
24728
|
+
const { [MEASURED_KEY]: _measured, ...rest } = data;
|
|
24729
|
+
return rest;
|
|
24730
|
+
}
|
|
24576
24731
|
var NAME_KEYS = /* @__PURE__ */ new Set([KIND_FIELD, "label", "nodeTitle"]);
|
|
24577
24732
|
function oneLine(text, max = MAX_TEXT) {
|
|
24578
24733
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
@@ -24625,7 +24780,7 @@ function rowsLines(rows) {
|
|
|
24625
24780
|
function nodeLines(node, run) {
|
|
24626
24781
|
const kind = nodeKind(node);
|
|
24627
24782
|
const spec = kind ? KINDS[kind] : void 0;
|
|
24628
|
-
const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)})`;
|
|
24783
|
+
const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)}) ${sizeLabel(node)}`;
|
|
24629
24784
|
const facts = [];
|
|
24630
24785
|
const extra = [];
|
|
24631
24786
|
const d = node.data;
|
|
@@ -24690,7 +24845,7 @@ function boardSummary(snap) {
|
|
|
24690
24845
|
lines.push(`IA Lab: vinculado${label ? ` a ${String(label)}` : ""}.`);
|
|
24691
24846
|
}
|
|
24692
24847
|
if (doc.meta?.guide) lines.push(`Guia: ${oneLine(doc.meta.guide)}`);
|
|
24693
|
-
lines.push("", "N\xF3s:");
|
|
24848
|
+
lines.push("", "N\xF3s (tamanho: medido = como est\xE1 na tela; gravado = piso, o cart\xE3o pode ser maior):");
|
|
24694
24849
|
if (!nodes.length) lines.push("(vazio)");
|
|
24695
24850
|
for (const n of nodes) lines.push(...nodeLines(n, runs.get(n.id)));
|
|
24696
24851
|
lines.push("", "Liga\xE7\xF5es:");
|
|
@@ -24734,12 +24889,28 @@ function nodeDetail(node) {
|
|
|
24734
24889
|
}
|
|
24735
24890
|
return v;
|
|
24736
24891
|
};
|
|
24737
|
-
const json2 = JSON.stringify(strip(node.data, 0, ""));
|
|
24892
|
+
const json2 = JSON.stringify(strip(visibleData(node.data), 0, ""));
|
|
24738
24893
|
const note = cuts ? `
|
|
24739
24894
|
(${cuts} corte(s) marcado(s) com path=\u2026: leia inteiro com canvas_read_node e esse path)` : "";
|
|
24740
|
-
return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}"
|
|
24895
|
+
return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}" \xB7 ${sizeLabel(node)}
|
|
24741
24896
|
${json2}${note}`;
|
|
24742
24897
|
}
|
|
24898
|
+
var isMeasureNoise = (op) => op.type === "node.set" && typeof op.path === "string" && (op.path === `data.${MEASURED_KEY}` || op.path.startsWith(`data.${MEASURED_KEY}.`));
|
|
24899
|
+
function withoutMeasureNoise(envelopes) {
|
|
24900
|
+
const clean = (op) => {
|
|
24901
|
+
if (op.type === "batch" && Array.isArray(op.ops)) {
|
|
24902
|
+
const ops = op.ops.map((o) => clean(o)).filter((o) => o !== null);
|
|
24903
|
+
return ops.length ? { ...op, ops } : null;
|
|
24904
|
+
}
|
|
24905
|
+
return isMeasureNoise(op) ? null : op;
|
|
24906
|
+
};
|
|
24907
|
+
const out = [];
|
|
24908
|
+
for (const e of envelopes) {
|
|
24909
|
+
const op = clean(e.op);
|
|
24910
|
+
if (op) out.push({ ...e, op });
|
|
24911
|
+
}
|
|
24912
|
+
return out;
|
|
24913
|
+
}
|
|
24743
24914
|
function changesSince(envelopes, doc) {
|
|
24744
24915
|
const count = /* @__PURE__ */ new Map();
|
|
24745
24916
|
for (const e of envelopes) {
|
|
@@ -25049,6 +25220,158 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
|
|
|
25049
25220
|
);
|
|
25050
25221
|
}
|
|
25051
25222
|
|
|
25223
|
+
// src/tools/canvas-layout.ts
|
|
25224
|
+
var nodeIdArg = external_exports.string().min(1).max(128);
|
|
25225
|
+
var width = external_exports.number().finite().min(40).max(2e4);
|
|
25226
|
+
var ROLE_TEXT = {
|
|
25227
|
+
title: "t\xEDtulo",
|
|
25228
|
+
prompt: "prompt",
|
|
25229
|
+
image: "imagem",
|
|
25230
|
+
video: "v\xEDdeo",
|
|
25231
|
+
reference: "ref",
|
|
25232
|
+
frame: "frame",
|
|
25233
|
+
other: "outro"
|
|
25234
|
+
};
|
|
25235
|
+
function checkRows(doc, rows) {
|
|
25236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25237
|
+
for (const [i, row] of rows.entries()) {
|
|
25238
|
+
const ids = [...row.title ? [row.title] : [], ...row.left, ...row.right];
|
|
25239
|
+
if (!ids.length) throw new CanvasToolError(`rows[${i}] est\xE1 vazia: informe title, left ou right.`);
|
|
25240
|
+
for (const id of ids) {
|
|
25241
|
+
const node = doc.nodes[id];
|
|
25242
|
+
if (!node) throw new CanvasToolError(`rows[${i}]: o n\xF3 ${id} n\xE3o existe no board. Releia com canvas_read_board.`);
|
|
25243
|
+
if (node.data[KIND_FIELD] === "section") throw new CanvasToolError(`rows[${i}]: ${id} \xE9 uma Se\xE7\xE3o (fundo), n\xE3o entra no arranjo.`);
|
|
25244
|
+
if (seen.has(id)) throw new CanvasToolError(`O n\xF3 ${id} aparece duas vezes nas linhas: cada n\xF3 entra uma vez s\xF3.`);
|
|
25245
|
+
seen.add(id);
|
|
25246
|
+
}
|
|
25247
|
+
}
|
|
25248
|
+
}
|
|
25249
|
+
function layoutOps(doc, a) {
|
|
25250
|
+
const ops = [];
|
|
25251
|
+
for (const p of a.placed) {
|
|
25252
|
+
const node = doc.nodes[p.id];
|
|
25253
|
+
const set = (path, now, next) => {
|
|
25254
|
+
if (now !== next) ops.push({ type: "node.set", nodeId: p.id, path, value: setValue(next) });
|
|
25255
|
+
};
|
|
25256
|
+
set("position.x", node.position.x, p.x);
|
|
25257
|
+
set("position.y", node.position.y, p.y);
|
|
25258
|
+
set("size.w", node.size.w, p.floorW);
|
|
25259
|
+
}
|
|
25260
|
+
return ops;
|
|
25261
|
+
}
|
|
25262
|
+
function report(doc, a) {
|
|
25263
|
+
const name = (id) => `"${oneLine(nodeName(doc.nodes[id]), 36)}"`;
|
|
25264
|
+
const lines = [];
|
|
25265
|
+
const unmeasured = a.placed.filter((p) => p.source === "gravado");
|
|
25266
|
+
const estimated = a.placed.filter((p) => p.source === "medido" && p.estimated);
|
|
25267
|
+
if (unmeasured.length || estimated.length) {
|
|
25268
|
+
lines.push("A conferir (a altura usada pode n\xE3o ser a da tela):");
|
|
25269
|
+
if (unmeasured.length) {
|
|
25270
|
+
lines.push(
|
|
25271
|
+
`- sem medida da tela (usei o gravado): o cart\xE3o est\xE1 no piso, e a\xED o gravado \xE9 o da tela, ou o board n\xE3o abriu desde a \xFAltima mudan\xE7a, e a\xED pode ser maior: ${unmeasured.map((p) => name(p.id)).join(", ")}`
|
|
25272
|
+
);
|
|
25273
|
+
}
|
|
25274
|
+
if (estimated.length) {
|
|
25275
|
+
const how = estimated.map((p) => `${name(p.id)} (medido com ${Math.round(readMeasured(doc.nodes[p.id]).w)} de largura)`);
|
|
25276
|
+
lines.push(`- altura estimada com folga porque a largura mudou: ${how.join(", ")}`);
|
|
25277
|
+
}
|
|
25278
|
+
lines.push(
|
|
25279
|
+
"Se o board n\xE3o abriu desde a \xFAltima mudan\xE7a, abra (o Canvas mede o cart\xE3o que passa do piso) e rode o mesmo canvas_arrange_rows de novo para fechar sobra ou encosto."
|
|
25280
|
+
);
|
|
25281
|
+
} else {
|
|
25282
|
+
lines.push("Todos os n\xF3s arrumados t\xEAm medida da tela.");
|
|
25283
|
+
}
|
|
25284
|
+
const pairs = (list) => list.map(([x, y]) => `${name(x)} \xD7 ${name(y)}`).join("; ");
|
|
25285
|
+
lines.push(
|
|
25286
|
+
a.inner.length || a.outer.length ? `Sobreposi\xE7\xF5es com os tamanhos usados: ${pairs([...a.inner, ...a.outer])}.` : "Sobreposi\xE7\xF5es com os tamanhos usados: nenhuma."
|
|
25287
|
+
);
|
|
25288
|
+
lines.push('Caixas ([papel] id "nome" @(x,y) largura\xD7altura usada, origem da altura):');
|
|
25289
|
+
const rows = /* @__PURE__ */ new Map();
|
|
25290
|
+
for (const p of a.placed) {
|
|
25291
|
+
const item = `[${ROLE_TEXT[p.role]}] ${p.id} ${name(p.id)} @(${p.x},${p.y}) ${p.w}\xD7${p.h} ${p.source}${p.estimated ? " estimado" : ""}`;
|
|
25292
|
+
rows.set(p.row, [...rows.get(p.row) ?? [], item]);
|
|
25293
|
+
}
|
|
25294
|
+
for (const [r, items] of rows) lines.push(`- linha ${r + 1}: ${items.join(" \xB7 ")}`);
|
|
25295
|
+
const resized = a.placed.filter((p) => p.floorW !== doc.nodes[p.id].size.w);
|
|
25296
|
+
if (resized.length) lines.push(`Largura (size.w): ${resized.map((p) => `${name(p.id)} ${doc.nodes[p.id].size.w}\u2192${p.floorW}`).join(", ")}.`);
|
|
25297
|
+
const titles = a.placed.filter((p) => p.role === "title" && doc.nodes[p.id].data[KIND_FIELD] === "label" && doc.nodes[p.id].data.size !== "titulo");
|
|
25298
|
+
if (titles.length) {
|
|
25299
|
+
lines.push(
|
|
25300
|
+
`T\xEDtulo sem o texto de t\xEDtulo: ${titles.map((p) => name(p.id)).join(", ")}. Troque com canvas_update_node size:"titulo" (o arranjo s\xF3 grava posi\xE7\xE3o e tamanho).`
|
|
25301
|
+
);
|
|
25302
|
+
}
|
|
25303
|
+
return lines.join("\n");
|
|
25304
|
+
}
|
|
25305
|
+
function registerCanvasLayoutTools(server, deps) {
|
|
25306
|
+
const { api, run, edit } = deps;
|
|
25307
|
+
server.registerTool(
|
|
25308
|
+
"canvas_arrange_rows",
|
|
25309
|
+
{
|
|
25310
|
+
title: "Canvas: arrumar em linhas",
|
|
25311
|
+
description: "Arruma n\xF3s em linhas, uma por vers\xE3o (revis\xE3o de imagem) ou por take (anima\xE7\xE3o), de cima para baixo a partir de origin: title em cima; \xE0 esquerda a coluna left (prompt; m\xEDdia/imagem que alimenta o prompt ou o gerador, como a refer\xEAncia e o frame de entrada, fica alinhada \xE0 direita embaixo dele); \xE0 direita a coluna right (gerador) com o topo no do prompt. Qualquer tipo entra em qualquer coluna. Usa o tamanho medido da tela (ou o gravado, avisando) e p\xF5e a largura pelo tipo real do n\xF3: prompt 1024, gerador de imagem 560, de v\xEDdeo 560, refer\xEAncia (m\xEDdia) 320, frame (m\xEDdia/imagem ligada na porta frame do v\xEDdeo) 320, t\xEDtulo 1024. Grava s\xF3 posi\xE7\xE3o e tamanho num passo, sem pedir releitura. Devolve as caixas, as sobreposi\xE7\xF5es com os tamanhos usados e os n\xF3s sem medida (a altura deles pode crescer quando o board abrir: rode de novo depois). Se a \xE1rea encostar em outro n\xF3 do board, n\xE3o grava nada e sugere uma origem livre. dry_run: true s\xF3 calcula.",
|
|
25312
|
+
inputSchema: {
|
|
25313
|
+
board_id: external_exports.string().uuid(),
|
|
25314
|
+
origin: external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() }).describe("Canto de cima \xE0 esquerda da \xE1rea."),
|
|
25315
|
+
rows: external_exports.array(
|
|
25316
|
+
external_exports.object({
|
|
25317
|
+
title: nodeIdArg.optional().describe("N\xF3 do t\xEDtulo da linha (Texto livre)."),
|
|
25318
|
+
left: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da esquerda, de cima para baixo: prompt, depois a refer\xEAncia."),
|
|
25319
|
+
right: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da direita: gerador(es).")
|
|
25320
|
+
}).strict()
|
|
25321
|
+
).min(1).max(50),
|
|
25322
|
+
gap: external_exports.number().finite().min(0).max(1e3).optional().describe(`Espa\xE7o entre cart\xF5es (padr\xE3o ${DEFAULT_GAP}); o dobro entre linhas.`),
|
|
25323
|
+
widths: external_exports.object({
|
|
25324
|
+
prompt: width.optional(),
|
|
25325
|
+
image: width.optional(),
|
|
25326
|
+
video: width.optional(),
|
|
25327
|
+
generator: width.optional().describe("Atalho: image e video juntos."),
|
|
25328
|
+
reference: width.optional(),
|
|
25329
|
+
frame: width.optional(),
|
|
25330
|
+
title: width.optional()
|
|
25331
|
+
}).strict().optional().describe("Troca larguras da conven\xE7\xE3o (prompt 1024, image 560, video 560, reference 320, frame 320, title 1024)."),
|
|
25332
|
+
dry_run: external_exports.boolean().optional().describe("true: s\xF3 calcula e mostra, sem gravar.")
|
|
25333
|
+
},
|
|
25334
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
25335
|
+
},
|
|
25336
|
+
async (args) => run(async () => {
|
|
25337
|
+
const rows = args.rows.map((r) => ({ title: r.title, left: r.left ?? [], right: r.right ?? [] }));
|
|
25338
|
+
const { generator, ...own } = args.widths ?? {};
|
|
25339
|
+
const widths = { ...DEFAULT_WIDTHS, ...generator ? { image: generator, video: generator } : {}, ...own };
|
|
25340
|
+
const gap = args.gap ?? DEFAULT_GAP;
|
|
25341
|
+
const plan = (doc) => {
|
|
25342
|
+
checkRows(doc, rows);
|
|
25343
|
+
const a = arrangeRows(doc, rows, args.origin, gap, widths);
|
|
25344
|
+
return { a, ops: layoutOps(doc, a), text: report(doc, a) };
|
|
25345
|
+
};
|
|
25346
|
+
const count = (ops) => new Set(ops.map((o) => o.type === "node.set" ? o.nodeId : "")).size;
|
|
25347
|
+
const blocked = (a) => `A \xE1rea encosta em n\xF3s que n\xE3o est\xE3o no arranjo. Nada foi gravado. Origem livre sugerida: {"x":${a.freeOrigin.x},"y":${a.freeOrigin.y}} (abaixo de tudo nessa faixa), ou tire esses n\xF3s do caminho.`;
|
|
25348
|
+
if (args.dry_run) {
|
|
25349
|
+
const { a, ops, text: text2 } = plan((await api.snapshot(args.board_id)).doc);
|
|
25350
|
+
const head = `Pr\xE9via, nada gravado: ${ops.length ? `${count(ops)} n\xF3(s) mudariam de lugar ou largura` : "tudo j\xE1 est\xE1 assim"}.`;
|
|
25351
|
+
return [head, ...a.outer.length ? [blocked(a)] : [], text2].join("\n");
|
|
25352
|
+
}
|
|
25353
|
+
let text = "";
|
|
25354
|
+
const done = await edit(
|
|
25355
|
+
args.board_id,
|
|
25356
|
+
async (doc) => {
|
|
25357
|
+
const p = plan(doc);
|
|
25358
|
+
text = p.text;
|
|
25359
|
+
if (p.a.outer.length) throw new CanvasToolError(`${blocked(p.a)}
|
|
25360
|
+
${p.text}`);
|
|
25361
|
+
return {
|
|
25362
|
+
ops: p.ops,
|
|
25363
|
+
targets: [],
|
|
25364
|
+
done: `Arranjo: ${rows.length} linha(s), ${p.a.placed.length} n\xF3(s), ${count(p.ops)} mudaram de lugar ou largura`
|
|
25365
|
+
};
|
|
25366
|
+
},
|
|
25367
|
+
{ split: true, layoutOnly: true }
|
|
25368
|
+
);
|
|
25369
|
+
return `${done}
|
|
25370
|
+
${text}`;
|
|
25371
|
+
})
|
|
25372
|
+
);
|
|
25373
|
+
}
|
|
25374
|
+
|
|
25052
25375
|
// src/canvas/upload.ts
|
|
25053
25376
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
25054
25377
|
import { basename as basename2, extname as extname3 } from "path";
|
|
@@ -25257,6 +25580,137 @@ ${payload(u)}`
|
|
|
25257
25580
|
);
|
|
25258
25581
|
}
|
|
25259
25582
|
|
|
25583
|
+
// src/canvas/mentions.ts
|
|
25584
|
+
var VIA_MENTION = "viaMention";
|
|
25585
|
+
var PROMPT_REFS_INPUT = "refs";
|
|
25586
|
+
var PROMPT_MENTION_REFS_INPUT = "mentionRefs";
|
|
25587
|
+
var MENTION_KEYS = ["template", "fields", "mentions"];
|
|
25588
|
+
var VARIABLE_SOURCE = String.raw`\{\{[ \t]*([\p{L}\p{N}_-]+)[ \t]*\}\}`;
|
|
25589
|
+
function extractVariables(text) {
|
|
25590
|
+
const names = /* @__PURE__ */ new Set();
|
|
25591
|
+
for (const m of text.matchAll(new RegExp(VARIABLE_SOURCE, "gu"))) if (m[1]) names.add(m[1]);
|
|
25592
|
+
return [...names];
|
|
25593
|
+
}
|
|
25594
|
+
function allVariableNames(data) {
|
|
25595
|
+
const names = new Set(extractVariables(typeof data.template === "string" ? data.template : ""));
|
|
25596
|
+
const fields = data.fields;
|
|
25597
|
+
if (fields && typeof fields === "object" && !Array.isArray(fields)) {
|
|
25598
|
+
for (const [id, text] of Object.entries(fields)) {
|
|
25599
|
+
if (id !== "" && typeof text === "string" && text !== "") for (const n of extractVariables(text)) names.add(n);
|
|
25600
|
+
}
|
|
25601
|
+
}
|
|
25602
|
+
return [...names];
|
|
25603
|
+
}
|
|
25604
|
+
function normalizeMentions(value) {
|
|
25605
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
25606
|
+
const out = {};
|
|
25607
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
25608
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
25609
|
+
const { node, port } = entry;
|
|
25610
|
+
if (name.trim() !== "" && typeof node === "string" && node !== "" && typeof port === "string" && port !== "") {
|
|
25611
|
+
out[name] = { node, port };
|
|
25612
|
+
}
|
|
25613
|
+
}
|
|
25614
|
+
return out;
|
|
25615
|
+
}
|
|
25616
|
+
function mentionEdgeId(e) {
|
|
25617
|
+
return `mention:${e.source}:${e.sourcePort}>${e.target}:${e.targetPort}`;
|
|
25618
|
+
}
|
|
25619
|
+
var edgeKey = (e) => `${e.source}\0${e.sourcePort}\0${e.target}\0${e.targetPort}`;
|
|
25620
|
+
var isViaMention = (e) => e.data[VIA_MENTION] === true;
|
|
25621
|
+
function reachability(doc) {
|
|
25622
|
+
const adj = /* @__PURE__ */ new Map();
|
|
25623
|
+
const link = (s, t) => adj.set(s, (adj.get(s) ?? /* @__PURE__ */ new Set()).add(t));
|
|
25624
|
+
for (const e of Object.values(doc.edges)) link(e.source, e.target);
|
|
25625
|
+
const reaches = (from, to) => {
|
|
25626
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25627
|
+
const stack = [from];
|
|
25628
|
+
while (stack.length) {
|
|
25629
|
+
for (const next of adj.get(stack.pop()) ?? []) {
|
|
25630
|
+
if (next === to) return true;
|
|
25631
|
+
if (!seen.has(next)) {
|
|
25632
|
+
seen.add(next);
|
|
25633
|
+
stack.push(next);
|
|
25634
|
+
}
|
|
25635
|
+
}
|
|
25636
|
+
}
|
|
25637
|
+
return false;
|
|
25638
|
+
};
|
|
25639
|
+
return { wouldCycle: (s, t) => s === t || reaches(t, s), accept: link };
|
|
25640
|
+
}
|
|
25641
|
+
function originHasPort(doc, origin) {
|
|
25642
|
+
const node = doc.nodes[origin.node];
|
|
25643
|
+
if (!node) return false;
|
|
25644
|
+
const kind = typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : null;
|
|
25645
|
+
if (!kind || !KINDS[kind]) return false;
|
|
25646
|
+
return knownPort(kind, "out", origin.port) !== false;
|
|
25647
|
+
}
|
|
25648
|
+
function mentionEdgeOps(doc, promptIds, actorId, now = Date.now()) {
|
|
25649
|
+
const out = { ops: [], added: [], removed: [], skipped: [] };
|
|
25650
|
+
const reach = reachability(doc);
|
|
25651
|
+
const edges = Object.values(doc.edges);
|
|
25652
|
+
const desired = /* @__PURE__ */ new Map();
|
|
25653
|
+
const managed = [];
|
|
25654
|
+
for (const id of [...new Set(promptIds)]) {
|
|
25655
|
+
const prompt = doc.nodes[id];
|
|
25656
|
+
if (!prompt || prompt.data[KIND_FIELD] !== "prompt") continue;
|
|
25657
|
+
const into = edges.filter((e) => e.target === id);
|
|
25658
|
+
const vars = new Set(allVariableNames(prompt.data));
|
|
25659
|
+
const mentions2 = normalizeMentions(prompt.data.mentions);
|
|
25660
|
+
const mentionPorts = new Set(into.filter(isViaMention).map((e) => e.targetPort));
|
|
25661
|
+
managed.push(...into.filter((e) => isViaMention(e) && e.targetPort !== PROMPT_REFS_INPUT && e.targetPort !== PROMPT_MENTION_REFS_INPUT));
|
|
25662
|
+
for (const port of /* @__PURE__ */ new Set([...vars, ...mentionPorts])) {
|
|
25663
|
+
if (port === PROMPT_REFS_INPUT || port === PROMPT_MENTION_REFS_INPUT) continue;
|
|
25664
|
+
const origin = vars.has(port) ? mentions2[port] : void 0;
|
|
25665
|
+
if (!origin) continue;
|
|
25666
|
+
if (into.some((e) => e.targetPort === port && !isViaMention(e))) {
|
|
25667
|
+
out.skipped.push(`{{${port}}}: a porta j\xE1 recebe uma liga\xE7\xE3o feita \xE0 m\xE3o`);
|
|
25668
|
+
continue;
|
|
25669
|
+
}
|
|
25670
|
+
if (!doc.nodes[origin.node]) {
|
|
25671
|
+
out.skipped.push(`{{${port}}}: o n\xF3 de origem ${origin.node} n\xE3o existe`);
|
|
25672
|
+
continue;
|
|
25673
|
+
}
|
|
25674
|
+
if (!originHasPort(doc, origin)) {
|
|
25675
|
+
out.skipped.push(`{{${port}}}: a porta "${origin.port}" n\xE3o existe em ${origin.node}`);
|
|
25676
|
+
continue;
|
|
25677
|
+
}
|
|
25678
|
+
if (reach.wouldCycle(origin.node, id)) {
|
|
25679
|
+
out.skipped.push(`{{${port}}}: a liga\xE7\xE3o fecharia um ciclo`);
|
|
25680
|
+
continue;
|
|
25681
|
+
}
|
|
25682
|
+
const link = { source: origin.node, sourcePort: origin.port, target: id, targetPort: port };
|
|
25683
|
+
desired.set(edgeKey(link), link);
|
|
25684
|
+
reach.accept(origin.node, id);
|
|
25685
|
+
}
|
|
25686
|
+
}
|
|
25687
|
+
for (const e of managed) {
|
|
25688
|
+
if (desired.has(edgeKey(e))) continue;
|
|
25689
|
+
out.ops.push({ type: "edge.remove", edgeId: e.id });
|
|
25690
|
+
out.removed.push(e);
|
|
25691
|
+
}
|
|
25692
|
+
for (const [key, link] of desired) {
|
|
25693
|
+
if (edges.some((e) => edgeKey(e) === key)) continue;
|
|
25694
|
+
const edgeId = mentionEdgeId(link);
|
|
25695
|
+
out.ops.push({
|
|
25696
|
+
type: "edge.add",
|
|
25697
|
+
edgeId,
|
|
25698
|
+
edge: { id: edgeId, ...link, data: { [VIA_MENTION]: true }, createdBy: actorId, createdAt: now }
|
|
25699
|
+
});
|
|
25700
|
+
out.added.push(link);
|
|
25701
|
+
}
|
|
25702
|
+
return out;
|
|
25703
|
+
}
|
|
25704
|
+
function describeMentionSync(sync, name) {
|
|
25705
|
+
const parts = [];
|
|
25706
|
+
if (sync.added.length) {
|
|
25707
|
+
parts.push(`liga\xE7\xE3o de men\xE7\xE3o criada: ${sync.added.map((l) => `${name(l.source)}.${l.sourcePort} \u2192 {{${l.targetPort}}}`).join(", ")}`);
|
|
25708
|
+
}
|
|
25709
|
+
if (sync.removed.length) parts.push(`liga\xE7\xE3o de men\xE7\xE3o removida: ${sync.removed.map((e) => `{{${e.targetPort}}}`).join(", ")}`);
|
|
25710
|
+
if (sync.skipped.length) parts.push(`men\xE7\xE3o sem liga\xE7\xE3o: ${sync.skipped.join("; ")}`);
|
|
25711
|
+
return parts.length ? ` ${parts.join(". ")}.` : "";
|
|
25712
|
+
}
|
|
25713
|
+
|
|
25260
25714
|
// src/tools/canvas.ts
|
|
25261
25715
|
var REJECT_TEXT = {
|
|
25262
25716
|
"node-missing": "o n\xF3 n\xE3o existe mais (algu\xE9m apagou)",
|
|
@@ -25265,6 +25719,7 @@ var REJECT_TEXT = {
|
|
|
25265
25719
|
"too-large": "opera\xE7\xE3o grande demais (64 KB) ou com m\xEDdia embutida"
|
|
25266
25720
|
};
|
|
25267
25721
|
var READ = "Exige canvas_read_board nesta sess\xE3o (e de novo depois de uma recusa). ";
|
|
25722
|
+
var MENTION_NOTE = 'Prompt: {{var}} no template + data.mentions {var: {node, port}} (o !! de um Bloco \xE9 port "text") j\xE1 cria, no mesmo passo, a liga\xE7\xE3o fina de men\xE7\xE3o que o Canvas desenha; tirar a var ou a men\xE7\xE3o tira a liga\xE7\xE3o. ';
|
|
25268
25723
|
var MONTAGE_NOTE = "Montagem: musicGainDb, duckingDb e fadeMs valem s\xF3 para a montagem autom\xE1tica (clipes ligados) e n\xE3o mexem na timeline do editor (data.timeline; data.sequence \xE9 o legado); o volume dela fica nos clipes (gainDb, gainKeyframes, fades) e nas faixas (gainDb de cada faixa, master).";
|
|
25269
25724
|
function dataSegments(path) {
|
|
25270
25725
|
const p = path?.trim() ?? "";
|
|
@@ -25283,8 +25738,26 @@ function legacySequenceGuard(node, key) {
|
|
|
25283
25738
|
var SECTION_Z = -1e3;
|
|
25284
25739
|
var GAP = 80;
|
|
25285
25740
|
var boardId2 = external_exports.string().uuid().describe("Id (uuid) do board, como sai em canvas_list_boards.");
|
|
25286
|
-
var
|
|
25741
|
+
var nodeIdArg2 = external_exports.string().min(1).max(128);
|
|
25287
25742
|
var point = external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() });
|
|
25743
|
+
var LABEL_TEXT_SIZES = ["titulo", "subtitulo", "corpo"];
|
|
25744
|
+
var dim = external_exports.number().finite().min(16).max(2e4);
|
|
25745
|
+
var sizeArg = external_exports.union([external_exports.object({ w: dim.optional(), h: dim.optional() }).strict(), external_exports.enum(LABEL_TEXT_SIZES)]).describe(
|
|
25746
|
+
'{w, h}: caixa do n\xF3 (piso: o cart\xE3o cresce com o conte\xFAdo; um lado s\xF3 vale). "titulo" | "subtitulo" | "corpo": tamanho do texto do Texto livre (label), gravado em data.size.'
|
|
25747
|
+
);
|
|
25748
|
+
var isLayoutOp = (op) => op.type === "batch" ? op.ops.every(isLayoutOp) : op.type === "node.set" && /^(position\.[xy]|size\.[wh])$/.test(op.path);
|
|
25749
|
+
function textSizeFor(kind, size) {
|
|
25750
|
+
if (kind !== "label") {
|
|
25751
|
+
throw new CanvasToolError(
|
|
25752
|
+
`size "${size}" \xE9 o tamanho do texto do Texto livre (label); para ${kind ?? "este n\xF3"} use size {w, h}.`
|
|
25753
|
+
);
|
|
25754
|
+
}
|
|
25755
|
+
return size;
|
|
25756
|
+
}
|
|
25757
|
+
function boxOf(size) {
|
|
25758
|
+
if (size.w === void 0 && size.h === void 0) throw new CanvasToolError("size {w, h}: informe w, h ou os dois.");
|
|
25759
|
+
return size;
|
|
25760
|
+
}
|
|
25288
25761
|
function registerCanvasTools(server, client, cfg) {
|
|
25289
25762
|
const api = new CanvasApi(client, cfg);
|
|
25290
25763
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -25308,7 +25781,7 @@ function registerCanvasTools(server, client, cfg) {
|
|
|
25308
25781
|
}
|
|
25309
25782
|
async function edit(id, build, opts = {}) {
|
|
25310
25783
|
const last = seen.get(id);
|
|
25311
|
-
if (last === void 0) {
|
|
25784
|
+
if (last === void 0 && !opts.layoutOnly) {
|
|
25312
25785
|
throw new CanvasToolError("Leia o board com canvas_read_board antes de editar (a leitura vale para a sess\xE3o).");
|
|
25313
25786
|
}
|
|
25314
25787
|
const snap = await api.snapshot(id);
|
|
@@ -25316,10 +25789,11 @@ function registerCanvasTools(server, client, cfg) {
|
|
|
25316
25789
|
const doc = snap.doc;
|
|
25317
25790
|
const { ops, done, targets } = await build(doc, snap);
|
|
25318
25791
|
if (!ops.length) return `Nada a mudar. ${done}`;
|
|
25792
|
+
if (opts.layoutOnly && !ops.every(isLayoutOp)) throw new Error("layout_only com opera\xE7\xE3o fora de posi\xE7\xE3o/tamanho");
|
|
25319
25793
|
let note = "";
|
|
25320
|
-
if (snap.seq > last) {
|
|
25794
|
+
if (!opts.layoutOnly && last !== void 0 && snap.seq > last) {
|
|
25321
25795
|
const since = await api.opsAfter(id, last);
|
|
25322
|
-
const theirs = since
|
|
25796
|
+
const theirs = since === null ? null : withoutMeasureNoise(since.filter((e) => e.clientId !== CLIENT_ID));
|
|
25323
25797
|
if (theirs === null) {
|
|
25324
25798
|
seen.delete(id);
|
|
25325
25799
|
throw new CanvasToolError(
|
|
@@ -25337,21 +25811,21 @@ function registerCanvasTools(server, client, cfg) {
|
|
|
25337
25811
|
if (theirs.length) note = `
|
|
25338
25812
|
Aviso: ${changesSince(theirs, doc)}.`;
|
|
25339
25813
|
}
|
|
25340
|
-
if (opts.split) return commitSplit(id, ops, done, note);
|
|
25814
|
+
if (opts.split) return commitSplit(id, ops, done, note, !!opts.layoutOnly);
|
|
25341
25815
|
const env = envelope(id, batchOf(ops), await api.me());
|
|
25342
25816
|
const bad = checkEnvelope(env);
|
|
25343
25817
|
if (bad) throw new CanvasToolError(`Edi\xE7\xE3o recusada antes de enviar: ${bad}.`);
|
|
25344
25818
|
const res = await api.commit(id, [env]);
|
|
25345
25819
|
const outcome = res.outcomes.find((o) => o.opId === env.id);
|
|
25346
25820
|
if (!outcome || "rejected" in outcome) {
|
|
25347
|
-
seen.delete(id);
|
|
25821
|
+
if (!opts.layoutOnly) seen.delete(id);
|
|
25348
25822
|
const why = outcome && "rejected" in outcome ? REJECT_TEXT[outcome.rejected] ?? outcome.rejected : "sem resposta do servidor";
|
|
25349
25823
|
throw new CanvasToolError(`O servidor recusou a edi\xE7\xE3o: ${why}. Nada foi aplicado. Releia com canvas_read_board.`);
|
|
25350
25824
|
}
|
|
25351
|
-
seen.set(id, res.seq);
|
|
25825
|
+
if (!opts.layoutOnly) seen.set(id, res.seq);
|
|
25352
25826
|
return `\u2705 ${done} (seq ${res.seq}).${note}`;
|
|
25353
25827
|
}
|
|
25354
|
-
async function commitSplit(id, ops, done, note) {
|
|
25828
|
+
async function commitSplit(id, ops, done, note, layoutOnly = false) {
|
|
25355
25829
|
let envs;
|
|
25356
25830
|
try {
|
|
25357
25831
|
envs = packEnvelopes(id, ops, await api.me());
|
|
@@ -25373,7 +25847,8 @@ Aviso: ${changesSince(theirs, doc)}.`;
|
|
|
25373
25847
|
for (const o of res.outcomes) outcomes.set(o.opId, o);
|
|
25374
25848
|
last = res.seq;
|
|
25375
25849
|
}
|
|
25376
|
-
const
|
|
25850
|
+
const setNodes = new Set(ops.flatMap((op) => op.type === "batch" ? op.ops : [op]).flatMap((op) => op.type === "node.set" ? [op.nodeId] : []));
|
|
25851
|
+
const pathsOf = (op) => op.type === "batch" ? op.ops.flatMap(pathsOf) : op.type === "node.set" ? [`${setNodes.size > 1 ? `${op.nodeId}:` : ""}${op.path.replace(/^data\./, "")}`] : [op.type];
|
|
25377
25852
|
let failed = 0;
|
|
25378
25853
|
const lines = envs.map((env, i) => {
|
|
25379
25854
|
const paths = pathsOf(env.op);
|
|
@@ -25384,14 +25859,14 @@ Aviso: ${changesSince(theirs, doc)}.`;
|
|
|
25384
25859
|
return `- envelope ${i + 1}/${envs.length}: ${paths.length} caminho(s) (${span}), ${kb} KB \u2192 ${result}`;
|
|
25385
25860
|
});
|
|
25386
25861
|
if (failed) {
|
|
25387
|
-
seen.delete(id);
|
|
25862
|
+
if (!layoutOnly) seen.delete(id);
|
|
25388
25863
|
throw new CanvasToolError(
|
|
25389
25864
|
`${done}: ${envs.length - failed} de ${envs.length} envelope(s) entraram, ${failed} recusado(s).
|
|
25390
25865
|
${lines.join("\n")}
|
|
25391
|
-
Os aceitos ficam. Releia com canvas_read_board e canvas_read_node e reenvie s\xF3 os caminhos dos envelopes recusados
|
|
25866
|
+
` + (layoutOnly ? "Os aceitos ficam. Rode de novo: posi\xE7\xE3o e tamanho n\xE3o pedem releitura." : "Os aceitos ficam. Releia com canvas_read_board e canvas_read_node e reenvie s\xF3 os caminhos dos envelopes recusados.") + note
|
|
25392
25867
|
);
|
|
25393
25868
|
}
|
|
25394
|
-
if (last !== null) seen.set(id, last);
|
|
25869
|
+
if (last !== null && !layoutOnly) seen.set(id, last);
|
|
25395
25870
|
return `\u2705 ${done} em ${envs.length} envelope(s) (seq at\xE9 ${last}).
|
|
25396
25871
|
${lines.join("\n")}${note}`;
|
|
25397
25872
|
}
|
|
@@ -25445,11 +25920,53 @@ ${lines.join("\n")}${note}`;
|
|
|
25445
25920
|
const out = {};
|
|
25446
25921
|
for (const [k, v] of Object.entries(raw ?? {})) {
|
|
25447
25922
|
if (k === KIND_FIELD) throw new CanvasToolError("data.kind n\xE3o muda: apague o n\xF3 e crie outro do tipo certo.");
|
|
25923
|
+
if (k === MEASURED_KEY) {
|
|
25924
|
+
throw new CanvasToolError(`data.${MEASURED_KEY} \xE9 o tamanho medido pela tela do Canvas: s\xF3 o Canvas grava. Para mudar a caixa, use size {w, h}.`);
|
|
25925
|
+
}
|
|
25448
25926
|
if (!isJson(v)) throw new CanvasToolError(`data.${k} n\xE3o \xE9 JSON v\xE1lido.`);
|
|
25449
25927
|
out[k] = v;
|
|
25450
25928
|
}
|
|
25451
25929
|
return out;
|
|
25452
25930
|
}
|
|
25931
|
+
function changeOps(node, change) {
|
|
25932
|
+
const ops = [];
|
|
25933
|
+
const patch = parseData(change.data);
|
|
25934
|
+
for (const key of Object.keys(patch)) legacySequenceGuard(node, key);
|
|
25935
|
+
if (change.label) patch.label = change.label;
|
|
25936
|
+
let box;
|
|
25937
|
+
if (typeof change.size === "string") patch.size = textSizeFor(nodeKind(node), change.size);
|
|
25938
|
+
else if (change.size) box = boxOf(change.size);
|
|
25939
|
+
const data = { ...node.data };
|
|
25940
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
25941
|
+
const path = `data.${k}`;
|
|
25942
|
+
if (v === null) {
|
|
25943
|
+
if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
|
|
25944
|
+
delete data[k];
|
|
25945
|
+
} else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
|
|
25946
|
+
ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
|
|
25947
|
+
data[k] = v;
|
|
25948
|
+
}
|
|
25949
|
+
}
|
|
25950
|
+
const set = (path, now, next) => {
|
|
25951
|
+
if (next !== void 0 && next !== now) ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(next) });
|
|
25952
|
+
};
|
|
25953
|
+
set("position.x", node.position.x, change.position?.x);
|
|
25954
|
+
set("position.y", node.position.y, change.position?.y);
|
|
25955
|
+
set("size.w", node.size.w, box?.w);
|
|
25956
|
+
set("size.h", node.size.h, box?.h);
|
|
25957
|
+
const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "");
|
|
25958
|
+
return { ops, keys, data, dataKeys: Object.keys(patch) };
|
|
25959
|
+
}
|
|
25960
|
+
async function mentionOps(doc, changed, newEdges = []) {
|
|
25961
|
+
const prompts = changed.filter((c) => nodeKind(c.node) === "prompt" && c.keys.some((k) => MENTION_KEYS.includes(k)));
|
|
25962
|
+
if (!prompts.length) return { ops: [], note: "" };
|
|
25963
|
+
const after = { ...doc, nodes: { ...doc.nodes }, edges: { ...doc.edges } };
|
|
25964
|
+
for (const c of changed) after.nodes[c.node.id] = { ...c.node, data: c.data };
|
|
25965
|
+
for (const e of newEdges) after.edges[e.id] = e;
|
|
25966
|
+
const sync = mentionEdgeOps(after, prompts.map((c) => c.node.id), await api.me());
|
|
25967
|
+
const name = (id) => after.nodes[id] ? `"${oneLine(nodeName(after.nodes[id]), 40)}"` : id;
|
|
25968
|
+
return { ops: sync.ops, note: describeMentionSync(sync, name) };
|
|
25969
|
+
}
|
|
25453
25970
|
server.registerTool(
|
|
25454
25971
|
"canvas_list_boards",
|
|
25455
25972
|
{
|
|
@@ -25468,10 +25985,10 @@ ${lines.join("\n")}${note}`;
|
|
|
25468
25985
|
"canvas_read_board",
|
|
25469
25986
|
{
|
|
25470
25987
|
title: "Canvas: ler board",
|
|
25471
|
-
description:
|
|
25988
|
+
description: 'L\xEA um board: n\xF3s (id, tipo, nome, posi\xE7\xE3o, tamanho, campos principais), liga\xE7\xF5es e v\xEDnculo com o IA Lab, sem m\xEDdia. Tamanho "medido" \xE9 o da tela; "gravado" \xE9 o piso (o cart\xE3o cresce com o conte\xFAdo). Obrigat\xF3rio antes de editar (vale para a sess\xE3o). node_ids devolve o data desses n\xF3s, com cada corte marcado (path=\u2026); o valor inteiro (ex.: data.sequence da Montagem) sai no canvas_read_node.',
|
|
25472
25989
|
inputSchema: {
|
|
25473
25990
|
board_id: boardId2,
|
|
25474
|
-
node_ids: external_exports.array(
|
|
25991
|
+
node_ids: external_exports.array(nodeIdArg2).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
|
|
25475
25992
|
},
|
|
25476
25993
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
25477
25994
|
},
|
|
@@ -25493,7 +26010,7 @@ ${lines.join("\n")}${note}`;
|
|
|
25493
26010
|
description: 'Devolve o data de UM n\xF3 sem corte nenhum, em JSON compacto, para conferir e fazer diff. Na Montagem, sem path, vem a timeline do editor: data.timeline, ou data.sequence (legado) quando o n\xF3 ainda n\xE3o tem timeline, e o cabe\xE7alho diz qual veio. path escolhe um peda\xE7o ("items" da M\xEDdia, "timeline.meta"); "data" traz o data todo. Diz o tamanho total e o sha256; acima de max_chars vem em partes expl\xEDcitas (offset). N\xE3o substitui o canvas_read_board, que continua obrigat\xF3rio antes de editar. ' + MONTAGE_NOTE,
|
|
25494
26011
|
inputSchema: {
|
|
25495
26012
|
board_id: boardId2,
|
|
25496
|
-
node_id:
|
|
26013
|
+
node_id: nodeIdArg2,
|
|
25497
26014
|
path: external_exports.string().trim().max(200).optional().describe('Caminho dentro do data, com pontos (ex.: "timeline.meta", "items.0"). "data" = o data todo.'),
|
|
25498
26015
|
offset: external_exports.number().int().min(0).optional().describe("In\xEDcio da parte, em caracteres (padr\xE3o 0)."),
|
|
25499
26016
|
max_chars: external_exports.number().int().min(1e3).max(6e4).optional().describe("Tamanho da parte (padr\xE3o 40000).")
|
|
@@ -25504,8 +26021,11 @@ ${lines.join("\n")}${note}`;
|
|
|
25504
26021
|
const snap = await api.snapshot(board_id);
|
|
25505
26022
|
const node = snap.doc.nodes[node_id];
|
|
25506
26023
|
if (!node) throw new CanvasToolError(`N\xF3 ${node_id} n\xE3o existe no board. Releia com canvas_read_board.`);
|
|
25507
|
-
let value = node.data;
|
|
26024
|
+
let value = visibleData(node.data);
|
|
25508
26025
|
let segments = dataSegments(path);
|
|
26026
|
+
if (segments[0] === MEASURED_KEY) {
|
|
26027
|
+
throw new CanvasToolError(`data.${MEASURED_KEY} \xE9 interno do Canvas (tamanho medido da tela): ele sai no cabe\xE7alho desta leitura.`);
|
|
26028
|
+
}
|
|
25509
26029
|
let which = "";
|
|
25510
26030
|
if (!path?.trim() && nodeKind(node) === "montage") {
|
|
25511
26031
|
if (node.data.timeline !== void 0) {
|
|
@@ -25522,9 +26042,9 @@ ${lines.join("\n")}${note}`;
|
|
|
25522
26042
|
const here = value;
|
|
25523
26043
|
value = Array.isArray(here) ? here[Number(seg)] : here && typeof here === "object" ? here[seg] : void 0;
|
|
25524
26044
|
if (value === void 0) {
|
|
25525
|
-
const
|
|
26045
|
+
const at2 = segments.slice(0, i).join(".");
|
|
25526
26046
|
const keys = Array.isArray(here) ? `\xEDndices 0 a ${here.length - 1}` : here && typeof here === "object" ? Object.keys(here).join(", ") : "\u2014";
|
|
25527
|
-
throw new CanvasToolError(`"${seg}" n\xE3o existe em data${
|
|
26047
|
+
throw new CanvasToolError(`"${seg}" n\xE3o existe em data${at2 ? `.${at2}` : ""}. Existe: ${keys || "\u2014"}.`);
|
|
25528
26048
|
}
|
|
25529
26049
|
}
|
|
25530
26050
|
const json2 = JSON.stringify(value);
|
|
@@ -25534,7 +26054,8 @@ ${lines.join("\n")}${note}`;
|
|
|
25534
26054
|
const to = Math.min(from + size, total);
|
|
25535
26055
|
const hash = createHash("sha256").update(json2).digest("hex").slice(0, 12);
|
|
25536
26056
|
const where = `data${segments.length ? `.${segments.join(".")}` : ""}`;
|
|
25537
|
-
const
|
|
26057
|
+
const at = readMeasured(node)?.at;
|
|
26058
|
+
const head = `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" \xB7 board seq ${snap.seq} \xB7 tamanho ${sizeLabel(node)}${at ? ` \xB7 medido em ${at}` : ""} \xB7 ${where}${which}: ${total} caracteres (JSON compacto) \xB7 sha256 ${hash}`;
|
|
25538
26059
|
const part = from === 0 && to === total ? "Inteiro, abaixo." : `Parte: caracteres ${from} a ${to} de ${total}. ${to < total ? `Falta o resto: chame de novo com offset=${to}.` : "Esta \xE9 a \xFAltima parte."}`;
|
|
25539
26060
|
return `${head}
|
|
25540
26061
|
${part}
|
|
@@ -25595,15 +26116,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25595
26116
|
"canvas_add_node",
|
|
25596
26117
|
{
|
|
25597
26118
|
title: "Canvas: adicionar n\xF3",
|
|
25598
|
-
description: `Adiciona um n\xF3 (n\xE3o executa). ${READ}kind: ${kindList()}. data completa o padr\xE3o do tipo; label d\xE1 o nome. Sem position, nasce ao lado do n\xF3 ligado ou \xE0 direita do board. inputs/outputs j\xE1 ligam o n\xF3 no mesmo passo. ` + MONTAGE_NOTE,
|
|
26119
|
+
description: `Adiciona um n\xF3 (n\xE3o executa). ${READ}kind: ${kindList()}. data completa o padr\xE3o do tipo; label d\xE1 o nome. Sem position, nasce ao lado do n\xF3 ligado ou \xE0 direita do board. inputs/outputs j\xE1 ligam o n\xF3 no mesmo passo. ` + MENTION_NOTE + MONTAGE_NOTE,
|
|
25599
26120
|
inputSchema: {
|
|
25600
26121
|
board_id: boardId2,
|
|
25601
26122
|
kind: external_exports.string().min(1).max(40),
|
|
25602
26123
|
label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do cart\xE3o (data.label)."),
|
|
25603
26124
|
data: external_exports.record(external_exports.unknown()).optional().describe("Campos do data. Ex.: narration {blocks:[{id,text}]}, text {text}, prompt {template}."),
|
|
25604
26125
|
position: point.optional(),
|
|
25605
|
-
|
|
25606
|
-
|
|
26126
|
+
size: sizeArg.optional(),
|
|
26127
|
+
inputs: external_exports.array(external_exports.object({ from: nodeIdArg2, from_port: external_exports.string().min(1), to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que CHEGAM no n\xF3 novo: from.from_port \u2192 novo.to_port."),
|
|
26128
|
+
outputs: external_exports.array(external_exports.object({ from_port: external_exports.string().min(1), to: nodeIdArg2, to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que SAEM do n\xF3 novo: novo.from_port \u2192 to.to_port.")
|
|
25607
26129
|
},
|
|
25608
26130
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
25609
26131
|
},
|
|
@@ -25614,10 +26136,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25614
26136
|
const me = await api.me();
|
|
25615
26137
|
const data = { ...spec.data, ...parseData(args.data), [KIND_FIELD]: args.kind };
|
|
25616
26138
|
if (args.label) data.label = args.label;
|
|
26139
|
+
let box = spec.size;
|
|
26140
|
+
if (typeof args.size === "string") data.size = textSizeFor(args.kind, args.size);
|
|
26141
|
+
else if (args.size) {
|
|
26142
|
+
const b = boxOf(args.size);
|
|
26143
|
+
box = { w: b.w ?? spec.size.w, h: b.h ?? spec.size.h };
|
|
26144
|
+
}
|
|
25617
26145
|
const near = args.inputs?.[0] ? doc.nodes[args.inputs[0].from] : args.outputs?.[0] ? doc.nodes[args.outputs[0].to] : void 0;
|
|
25618
|
-
const position = args.position ?? placeFor(doc,
|
|
26146
|
+
const position = args.position ?? placeFor(doc, box, near, args.inputs?.[0] ? "right" : "left");
|
|
25619
26147
|
const zIndex = args.kind === "section" ? SECTION_Z : Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1;
|
|
25620
|
-
const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size:
|
|
26148
|
+
const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: box, zIndex, createdBy: me });
|
|
25621
26149
|
const links = [
|
|
25622
26150
|
...(args.inputs ?? []).map((l) => ({ source: l.from, sourcePort: l.from_port, target: node.id, targetPort: l.to_port })),
|
|
25623
26151
|
...(args.outputs ?? []).map((l) => ({ source: node.id, sourcePort: l.from_port, target: l.to, targetPort: l.to_port }))
|
|
@@ -25631,10 +26159,12 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25631
26159
|
}
|
|
25632
26160
|
}
|
|
25633
26161
|
const edges = edgeOps(doc, links, me, /* @__PURE__ */ new Set([node.id]));
|
|
26162
|
+
const drawn = edges.ops.flatMap((op) => op.type === "edge.add" ? [op.edge] : []);
|
|
26163
|
+
const mention = await mentionOps(doc, [{ node, data: node.data, keys: Object.keys(args.data ?? {}) }], drawn);
|
|
25634
26164
|
return {
|
|
25635
|
-
ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops],
|
|
26165
|
+
ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops, ...mention.ops],
|
|
25636
26166
|
targets: links.map((l) => l.source === node.id ? l.target : l.source),
|
|
25637
|
-
done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${edges.warn}`
|
|
26167
|
+
done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${edges.warn}${mention.note}`
|
|
25638
26168
|
};
|
|
25639
26169
|
})
|
|
25640
26170
|
)
|
|
@@ -25643,48 +26173,90 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25643
26173
|
"canvas_update_node",
|
|
25644
26174
|
{
|
|
25645
26175
|
title: "Canvas: editar n\xF3",
|
|
25646
|
-
description: `Muda campos do data de um n\xF3, chave a chave (null apaga a chave), sem executar. ${READ}label renomeia. N\xE3o troca o tipo. Cada chave \xE9 gravada inteira: leia antes com canvas_read_node e mande o valor completo j\xE1 revisado, para n\xE3o apagar o que outra pessoa escreveu. Para mudar partes de uma estrutura grande (clipes da timeline), use canvas_patch_node, por caminho. ` + MONTAGE_NOTE,
|
|
26176
|
+
description: `Muda campos do data de um n\xF3, chave a chave (null apaga a chave), sem executar. ${READ}label renomeia; position move; size {w, h} muda a caixa (piso: o cart\xE3o cresce com o conte\xFAdo). N\xE3o troca o tipo. Cada chave \xE9 gravada inteira: leia antes com canvas_read_node e mande o valor completo j\xE1 revisado, para n\xE3o apagar o que outra pessoa escreveu. Para mudar partes de uma estrutura grande (clipes da timeline), use canvas_patch_node, por caminho; v\xE1rios n\xF3s de uma vez, canvas_update_nodes. ` + MENTION_NOTE + MONTAGE_NOTE,
|
|
25647
26177
|
inputSchema: {
|
|
25648
26178
|
board_id: boardId2,
|
|
25649
|
-
node_id:
|
|
26179
|
+
node_id: nodeIdArg2,
|
|
25650
26180
|
data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave."),
|
|
25651
26181
|
label: external_exports.string().trim().min(1).max(120).optional(),
|
|
25652
|
-
position: point.optional()
|
|
26182
|
+
position: point.optional(),
|
|
26183
|
+
size: sizeArg.optional()
|
|
25653
26184
|
},
|
|
25654
26185
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
25655
26186
|
},
|
|
25656
26187
|
async (args) => run(
|
|
25657
26188
|
() => edit(args.board_id, async (doc) => {
|
|
25658
26189
|
const node = needNode(doc, args.node_id);
|
|
25659
|
-
const
|
|
25660
|
-
const
|
|
25661
|
-
|
|
25662
|
-
|
|
25663
|
-
|
|
25664
|
-
|
|
25665
|
-
|
|
25666
|
-
if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
|
|
25667
|
-
} else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
|
|
25668
|
-
ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
|
|
25669
|
-
}
|
|
25670
|
-
}
|
|
25671
|
-
if (args.position) {
|
|
25672
|
-
ops.push({ type: "node.set", nodeId: node.id, path: "position.x", value: setValue(args.position.x) });
|
|
25673
|
-
ops.push({ type: "node.set", nodeId: node.id, path: "position.y", value: setValue(args.position.y) });
|
|
25674
|
-
}
|
|
25675
|
-
const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "").join(", ");
|
|
25676
|
-
return { ops, targets: [node.id], done: `N\xF3 "${nodeName(node)}" atualizado: ${keys || "nada mudou"}` };
|
|
26190
|
+
const c = changeOps(node, args);
|
|
26191
|
+
const mention = await mentionOps(doc, [{ node, data: c.data, keys: c.dataKeys }]);
|
|
26192
|
+
return {
|
|
26193
|
+
ops: [...c.ops, ...mention.ops],
|
|
26194
|
+
targets: [node.id],
|
|
26195
|
+
done: `N\xF3 "${nodeName(node)}" atualizado: ${c.keys.join(", ") || "nada mudou"}${mention.note}`
|
|
26196
|
+
};
|
|
25677
26197
|
})
|
|
25678
26198
|
)
|
|
25679
26199
|
);
|
|
26200
|
+
server.registerTool(
|
|
26201
|
+
"canvas_update_nodes",
|
|
26202
|
+
{
|
|
26203
|
+
title: "Canvas: editar v\xE1rios n\xF3s",
|
|
26204
|
+
description: 'Muda v\xE1rios n\xF3s num passo s\xF3, cada um como no canvas_update_node (position, size, label, data com null apagando). layout_only: true aceita s\xF3 position e size {w, h}, n\xE3o exige leitura e n\xE3o recusa por "outra pessoa mexeu" (nesses dois campos vale quem grava por \xFAltimo). Sem layout_only, exige canvas_read_board nesta sess\xE3o e recusa se outra pessoa mexeu em algum desses n\xF3s. Quebra em envelopes de at\xE9 64 KB e relata cada um. Para arrumar linhas inteiras (t\xEDtulo, prompt, refer\xEAncia, gerador), use canvas_arrange_rows.',
|
|
26205
|
+
inputSchema: {
|
|
26206
|
+
board_id: boardId2,
|
|
26207
|
+
nodes: external_exports.array(
|
|
26208
|
+
external_exports.object({
|
|
26209
|
+
node_id: nodeIdArg2,
|
|
26210
|
+
position: point.optional(),
|
|
26211
|
+
size: sizeArg.optional(),
|
|
26212
|
+
label: external_exports.string().trim().min(1).max(120).optional(),
|
|
26213
|
+
data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave.")
|
|
26214
|
+
}).strict()
|
|
26215
|
+
).min(1).max(200),
|
|
26216
|
+
layout_only: external_exports.boolean().optional().describe("true: s\xF3 position e size, sem releitura nem recusa por conflito.")
|
|
26217
|
+
},
|
|
26218
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
26219
|
+
},
|
|
26220
|
+
async (args) => run(
|
|
26221
|
+
() => edit(
|
|
26222
|
+
args.board_id,
|
|
26223
|
+
async (doc) => {
|
|
26224
|
+
const ids = args.nodes.map((n) => n.node_id);
|
|
26225
|
+
const dup = ids.find((id, i) => ids.indexOf(id) !== i);
|
|
26226
|
+
if (dup) throw new CanvasToolError(`O n\xF3 ${dup} aparece duas vezes: junte as mudan\xE7as dele num item s\xF3.`);
|
|
26227
|
+
if (args.layout_only) {
|
|
26228
|
+
const bad = args.nodes.find((n) => n.data !== void 0 || n.label !== void 0 || typeof n.size === "string");
|
|
26229
|
+
if (bad) {
|
|
26230
|
+
throw new CanvasToolError(
|
|
26231
|
+
`layout_only aceita s\xF3 position e size {w, h} (o n\xF3 ${bad.node_id} traz ${bad.data !== void 0 ? "data" : bad.label !== void 0 ? "label" : `size "${String(bad.size)}"`}). Tire layout_only para gravar data e label com a checagem de conflito.`
|
|
26232
|
+
);
|
|
26233
|
+
}
|
|
26234
|
+
}
|
|
26235
|
+
const changes = args.nodes.map((n) => {
|
|
26236
|
+
const node = needNode(doc, n.node_id);
|
|
26237
|
+
return { node, ...changeOps(node, n) };
|
|
26238
|
+
});
|
|
26239
|
+
const mention = args.layout_only ? { ops: [], note: "" } : await mentionOps(doc, changes.map((c) => ({ node: c.node, data: c.data, keys: c.dataKeys })));
|
|
26240
|
+
const touched = changes.filter((c) => c.ops.length);
|
|
26241
|
+
const list = touched.slice(0, 12).map((c) => `"${oneLine(nodeName(c.node), 30)}" (${c.keys.join(", ")})`).join("; ");
|
|
26242
|
+
return {
|
|
26243
|
+
ops: [...changes.flatMap((c) => c.ops), ...mention.ops],
|
|
26244
|
+
targets: ids,
|
|
26245
|
+
done: `${touched.length} de ${ids.length} n\xF3(s) atualizado(s)${args.layout_only ? " (layout_only)" : ""}` + (list ? `: ${list}${touched.length > 12 ? `; \u2026 +${touched.length - 12}` : ""}` : "") + mention.note
|
|
26246
|
+
};
|
|
26247
|
+
},
|
|
26248
|
+
{ split: true, layoutOnly: !!args.layout_only }
|
|
26249
|
+
)
|
|
26250
|
+
)
|
|
26251
|
+
);
|
|
25680
26252
|
server.registerTool(
|
|
25681
26253
|
"canvas_patch_node",
|
|
25682
26254
|
{
|
|
25683
26255
|
title: "Canvas: gravar por caminho",
|
|
25684
|
-
description: `Grava v\xE1rios caminhos dentro do data de um n\xF3, sem executar: {path, value} grava, {path, unset: true} apaga. ${READ}O caminho atravessa objetos (o n\xEDvel que falta \xE9 criado) e n\xE3o entra em lista: para mudar um item de lista, grave a lista inteira. Quebra sozinho em envelopes de at\xE9 64 KB, em ordem, e devolve o resultado de cada um; entre envelopes n\xE3o h\xE1 atomicidade (o que entrou fica). Serve para mexer em partes de uma estrutura grande, como clipes da timeline da Montagem, sem reenviar tudo. Leia antes com canvas_read_node. ` + MONTAGE_NOTE,
|
|
26256
|
+
description: `Grava v\xE1rios caminhos dentro do data de um n\xF3, sem executar: {path, value} grava, {path, unset: true} apaga. ${READ}O caminho atravessa objetos (o n\xEDvel que falta \xE9 criado) e n\xE3o entra em lista: para mudar um item de lista, grave a lista inteira. Quebra sozinho em envelopes de at\xE9 64 KB, em ordem, e devolve o resultado de cada um; entre envelopes n\xE3o h\xE1 atomicidade (o que entrou fica). Serve para mexer em partes de uma estrutura grande, como clipes da timeline da Montagem, sem reenviar tudo. Leia antes com canvas_read_node. ` + MENTION_NOTE + MONTAGE_NOTE,
|
|
25685
26257
|
inputSchema: {
|
|
25686
26258
|
board_id: boardId2,
|
|
25687
|
-
node_id:
|
|
26259
|
+
node_id: nodeIdArg2,
|
|
25688
26260
|
sets: external_exports.array(
|
|
25689
26261
|
external_exports.object({
|
|
25690
26262
|
path: external_exports.string().trim().min(1).max(500).describe('Caminho dentro do data (ex.: "timeline.meta.fps"); aceita o prefixo "data.".'),
|
|
@@ -25704,6 +26276,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25704
26276
|
const ops = [];
|
|
25705
26277
|
let same = 0;
|
|
25706
26278
|
let absent = 0;
|
|
26279
|
+
const mentionKeys = /* @__PURE__ */ new Set();
|
|
26280
|
+
let mentionAt = -1;
|
|
25707
26281
|
for (const [i, item] of args.sets.entries()) {
|
|
25708
26282
|
const at = `sets[${i}] (${item.path})`;
|
|
25709
26283
|
const segs = dataSegments(item.path);
|
|
@@ -25713,6 +26287,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25713
26287
|
const path = `data.${segs.join(".")}`;
|
|
25714
26288
|
const badPath = checkNodePath(path);
|
|
25715
26289
|
if (badPath) throw new CanvasToolError(`${at}: ${badPath}.`);
|
|
26290
|
+
if (MENTION_KEYS.includes(segs[0])) mentionKeys.add(segs[0]);
|
|
25716
26291
|
const unset = item.unset === true;
|
|
25717
26292
|
const hasValue = item.value !== void 0;
|
|
25718
26293
|
if (unset === hasValue) throw new CanvasToolError(`${at}: mande value OU unset: true.`);
|
|
@@ -25747,6 +26322,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25747
26322
|
}
|
|
25748
26323
|
delete cur[leaf];
|
|
25749
26324
|
ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
|
|
26325
|
+
if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
|
|
25750
26326
|
continue;
|
|
25751
26327
|
}
|
|
25752
26328
|
const value = item.value;
|
|
@@ -25756,12 +26332,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25756
26332
|
}
|
|
25757
26333
|
if (cur) cur[leaf] = value;
|
|
25758
26334
|
ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(value) });
|
|
26335
|
+
if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
|
|
25759
26336
|
}
|
|
25760
26337
|
const skipped = [same ? `${same} igual(is) ao que j\xE1 estava` : "", absent ? `${absent} unset de caminho que n\xE3o existe` : ""].filter(Boolean).join(", ");
|
|
26338
|
+
const written = ops.length;
|
|
26339
|
+
const mention = await mentionOps(doc, [{ node, data: work, keys: [...mentionKeys] }]);
|
|
26340
|
+
ops.splice(mentionAt < 0 ? ops.length : mentionAt, 0, ...mention.ops);
|
|
25761
26341
|
return {
|
|
25762
26342
|
ops,
|
|
25763
26343
|
targets: [node.id],
|
|
25764
|
-
done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${
|
|
26344
|
+
done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${written} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "") + mention.note
|
|
25765
26345
|
};
|
|
25766
26346
|
},
|
|
25767
26347
|
{ split: true }
|
|
@@ -25775,9 +26355,9 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25775
26355
|
description: `Liga a sa\xEDda de um n\xF3 \xE0 entrada de outro (ids e portas de canvas_read_board). ${READ}Liga\xE7\xE3o repetida \xE9 ignorada.`,
|
|
25776
26356
|
inputSchema: {
|
|
25777
26357
|
board_id: boardId2,
|
|
25778
|
-
source:
|
|
26358
|
+
source: nodeIdArg2,
|
|
25779
26359
|
source_port: external_exports.string().min(1).max(80),
|
|
25780
|
-
target:
|
|
26360
|
+
target: nodeIdArg2,
|
|
25781
26361
|
target_port: external_exports.string().min(1).max(80)
|
|
25782
26362
|
},
|
|
25783
26363
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -25802,8 +26382,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25802
26382
|
description: `Apaga n\xF3s (com as liga\xE7\xF5es e varia\xE7\xF5es deles) e/ou liga\xE7\xF5es, num passo s\xF3. Irrevers\xEDvel pelo MCP: confirme com o usu\xE1rio antes. ${READ}N\xE3o apaga o board.`,
|
|
25803
26383
|
inputSchema: {
|
|
25804
26384
|
board_id: boardId2,
|
|
25805
|
-
node_ids: external_exports.array(
|
|
25806
|
-
edge_ids: external_exports.array(
|
|
26385
|
+
node_ids: external_exports.array(nodeIdArg2).max(200).optional(),
|
|
26386
|
+
edge_ids: external_exports.array(nodeIdArg2).max(200).optional()
|
|
25807
26387
|
},
|
|
25808
26388
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
25809
26389
|
},
|
|
@@ -25904,13 +26484,14 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
25904
26484
|
);
|
|
25905
26485
|
registerCanvasCommentTools(server, api, run);
|
|
25906
26486
|
registerCanvasMediaTools(server, { api, cfg, run, edit, seen, placeFor });
|
|
26487
|
+
registerCanvasLayoutTools(server, { api, run, edit });
|
|
25907
26488
|
}
|
|
25908
26489
|
|
|
25909
26490
|
// src/version.ts
|
|
25910
26491
|
import { createRequire } from "module";
|
|
25911
26492
|
var require2 = createRequire(import.meta.url);
|
|
25912
26493
|
function readVersion() {
|
|
25913
|
-
if ("0.
|
|
26494
|
+
if ("0.30.0") return "0.30.0";
|
|
25914
26495
|
try {
|
|
25915
26496
|
const pkg = require2("../package.json");
|
|
25916
26497
|
return pkg.version ?? "0.0.0-unknown";
|
|
@@ -25926,6 +26507,7 @@ var SERVER_INSTRUCTIONS = [
|
|
|
25926
26507
|
'M\xEDdia: generate_* gasta diamantes; confira com estimate_cost antes de lotes grandes. Sem project, a gera\xE7\xE3o cai no projeto ativo; billing:"personal" cobra do usu\xE1rio.',
|
|
25927
26508
|
"Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
|
|
25928
26509
|
"Canvas online (canvas_*): leia com canvas_read_board antes de editar e siga o estado lido. Cada edi\xE7\xE3o vai num passo s\xF3; se outra pessoa mexeu nos mesmos n\xF3s, a ferramenta recusa e pede releitura. As ferramentas canvas_* n\xE3o geram m\xEDdia nem gastam diamantes: quem roda os n\xF3s \xE9 o usu\xE1rio no Canvas. D\xEA nome (label) aos n\xF3s, n\xE3o invente tipos nem portas, e confirme antes de canvas_delete.",
|
|
26510
|
+
'Arrumar o board: o size gravado \xE9 piso (o cart\xE3o cresce com o conte\xFAdo); use o tamanho "medido" do canvas_read_board. \xC1rea de revis\xE3o: canvas_arrange_rows (uma linha por vers\xE3o); posi\xE7\xE3o/tamanho em lote: canvas_update_nodes com layout_only. N\xE3o afirme que nada se sobrep\xF5e com n\xF3 sem medida.',
|
|
25929
26511
|
'"Aplica os coment\xE1rios" do board: canvas_list_comments (abertos) \u2192 para cada um, leia o n\xF3 ancorado com canvas_read_board (node_ids), aplique a edi\xE7\xE3o pelas canvas_* e feche com canvas_resolve_comment dizendo em uma frase o que fez. Coment\xE1rio amb\xEDguo ou com alvo sumido: responda com pergunta (canvas_reply_comment) e n\xE3o resolva. D\xFAvida sua sobre um n\xF3: canvas_comment ancorado nele. As escritas j\xE1 saem espa\xE7adas (60/min); se ainda vier limite, pare e diga de onde seguir.'
|
|
25930
26512
|
].join("\n");
|
|
25931
26513
|
function buildServer(cfg) {
|
|
@@ -26020,7 +26602,7 @@ function serversNaming(entry) {
|
|
|
26020
26602
|
if (!servers || typeof servers !== "object") return [];
|
|
26021
26603
|
return Object.entries(servers).filter(([name, value]) => name === "beplus" || JSON.stringify(value ?? null).includes("beplus-mcp")).map(([name]) => name);
|
|
26022
26604
|
}
|
|
26023
|
-
function checkConfigs(
|
|
26605
|
+
function checkConfigs(report2) {
|
|
26024
26606
|
let found = 0;
|
|
26025
26607
|
for (const { path, client } of configCandidates()) {
|
|
26026
26608
|
let raw;
|
|
@@ -26033,7 +26615,7 @@ function checkConfigs(report) {
|
|
|
26033
26615
|
try {
|
|
26034
26616
|
parsed = JSON.parse(raw);
|
|
26035
26617
|
} catch {
|
|
26036
|
-
|
|
26618
|
+
report2.line("aviso", `Config ilegivel: ${path}`, "O arquivo existe mas nao e JSON valido.");
|
|
26037
26619
|
continue;
|
|
26038
26620
|
}
|
|
26039
26621
|
const names = new Set(serversNaming(parsed));
|
|
@@ -26043,25 +26625,25 @@ function checkConfigs(report) {
|
|
|
26043
26625
|
}
|
|
26044
26626
|
if (names.size === 0) continue;
|
|
26045
26627
|
found += 1;
|
|
26046
|
-
|
|
26628
|
+
report2.line("ok", `Config detectado: ${path}`, `${client}, servidor ${[...names].join(", ")}`);
|
|
26047
26629
|
}
|
|
26048
26630
|
if (found === 0) {
|
|
26049
|
-
|
|
26631
|
+
report2.line(
|
|
26050
26632
|
"aviso",
|
|
26051
26633
|
"Nenhum config de cliente MCP com o beplus foi encontrado.",
|
|
26052
26634
|
"Isso e normal se voce ainda nao instalou o servidor, ou se o cliente guarda o config em outro lugar."
|
|
26053
26635
|
);
|
|
26054
26636
|
}
|
|
26055
26637
|
}
|
|
26056
|
-
async function checkApiReachable(
|
|
26638
|
+
async function checkApiReachable(report2, baseUrl) {
|
|
26057
26639
|
const controller = new AbortController();
|
|
26058
26640
|
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
26059
26641
|
try {
|
|
26060
26642
|
const res = await fetch(`${baseUrl}/api/v1/mcp/me`, { signal: controller.signal });
|
|
26061
|
-
|
|
26643
|
+
report2.line("ok", `API responde: ${baseUrl}`, `GET /api/v1/mcp/me sem token devolveu HTTP ${res.status}.`);
|
|
26062
26644
|
} catch (e) {
|
|
26063
26645
|
const aborted2 = e?.name === "AbortError";
|
|
26064
|
-
|
|
26646
|
+
report2.line(
|
|
26065
26647
|
"erro",
|
|
26066
26648
|
`API nao respondeu: ${baseUrl}`,
|
|
26067
26649
|
aborted2 ? "Tempo esgotado em 10s. Confira a rede, o proxy ou a VPN." : `Falha de rede: ${e?.message ?? "desconhecida"}. Confira a rede e o valor de BEPLUS_API_URL.`
|
|
@@ -26070,10 +26652,10 @@ async function checkApiReachable(report, baseUrl) {
|
|
|
26070
26652
|
clearTimeout(timer);
|
|
26071
26653
|
}
|
|
26072
26654
|
}
|
|
26073
|
-
async function checkToken(
|
|
26655
|
+
async function checkToken(report2) {
|
|
26074
26656
|
const token = process.env.BEPLUS_API_TOKEN?.trim();
|
|
26075
26657
|
if (!token) {
|
|
26076
|
-
|
|
26658
|
+
report2.line(
|
|
26077
26659
|
"aviso",
|
|
26078
26660
|
"BEPLUS_API_TOKEN nao esta definido.",
|
|
26079
26661
|
"Gere um Personal Access Token em Tokens de acesso, no painel BePlus, e coloque no config do MCP."
|
|
@@ -26082,46 +26664,46 @@ async function checkToken(report) {
|
|
|
26082
26664
|
}
|
|
26083
26665
|
try {
|
|
26084
26666
|
const me = await new BeplusClient(loadConfig()).me();
|
|
26085
|
-
|
|
26667
|
+
report2.line(
|
|
26086
26668
|
"ok",
|
|
26087
26669
|
`Token valido (${maskToken(token)})`,
|
|
26088
26670
|
`Conta: ${me.name ?? me.email ?? me.userId}, ${me.diamonds} diamantes.`
|
|
26089
26671
|
);
|
|
26090
26672
|
} catch (e) {
|
|
26091
26673
|
if (e instanceof ApiError && e.httpStatus === 401) {
|
|
26092
|
-
|
|
26674
|
+
report2.line(
|
|
26093
26675
|
"erro",
|
|
26094
26676
|
`Token invalido ou revogado (${maskToken(token)})`,
|
|
26095
26677
|
"Gere um novo Personal Access Token no painel BePlus e atualize BEPLUS_API_TOKEN no config do MCP."
|
|
26096
26678
|
);
|
|
26097
26679
|
return;
|
|
26098
26680
|
}
|
|
26099
|
-
|
|
26681
|
+
report2.line("aviso", "Nao deu para validar o token agora.", e?.message ?? "erro desconhecido");
|
|
26100
26682
|
}
|
|
26101
26683
|
}
|
|
26102
26684
|
async function runDoctor() {
|
|
26103
|
-
const
|
|
26685
|
+
const report2 = new Report();
|
|
26104
26686
|
process.stdout.write(`beplus-mcp doctor, versao ${VERSION}
|
|
26105
26687
|
|
|
26106
26688
|
`);
|
|
26107
26689
|
if (isSupportedNode()) {
|
|
26108
|
-
|
|
26690
|
+
report2.line("ok", `Node ${process.versions.node}`, `Minimo exigido: Node ${MIN_NODE_MAJOR}.`);
|
|
26109
26691
|
} else {
|
|
26110
|
-
|
|
26692
|
+
report2.line("erro", `Node ${process.versions.node}`, unsupportedNodeMessage());
|
|
26111
26693
|
}
|
|
26112
26694
|
let baseUrl = null;
|
|
26113
26695
|
try {
|
|
26114
26696
|
baseUrl = resolveBaseUrl();
|
|
26115
|
-
|
|
26697
|
+
report2.line("ok", `BEPLUS_API_URL: ${baseUrl}`);
|
|
26116
26698
|
} catch (e) {
|
|
26117
|
-
|
|
26699
|
+
report2.line("erro", "BEPLUS_API_URL invalida", e.message);
|
|
26118
26700
|
}
|
|
26119
26701
|
if (baseUrl) {
|
|
26120
|
-
await checkApiReachable(
|
|
26121
|
-
await checkToken(
|
|
26702
|
+
await checkApiReachable(report2, baseUrl);
|
|
26703
|
+
await checkToken(report2);
|
|
26122
26704
|
}
|
|
26123
|
-
checkConfigs(
|
|
26124
|
-
const code =
|
|
26705
|
+
checkConfigs(report2);
|
|
26706
|
+
const code = report2.exitCode;
|
|
26125
26707
|
process.stdout.write(code === 0 ? "\nTudo certo.\n" : "\nTem item com [erro] acima. Resolva antes de usar o MCP.\n");
|
|
26126
26708
|
return code;
|
|
26127
26709
|
}
|