beplus-mcp 0.28.0 → 0.29.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/dist/index.js CHANGED
@@ -23004,7 +23004,7 @@ function probeWebp(b) {
23004
23004
  }
23005
23005
  function mp4Info(b) {
23006
23006
  let duration3;
23007
- let width = 0;
23007
+ let width2 = 0;
23008
23008
  let height = 0;
23009
23009
  let frames = 0;
23010
23010
  let mediaDuration;
@@ -23046,8 +23046,8 @@ function mp4Info(b) {
23046
23046
  const rotated = Math.abs(a) < 0.01 && Math.abs(d) < 0.01;
23047
23047
  const tw = Math.round(rotated ? h : w);
23048
23048
  const th = Math.round(rotated ? w : h);
23049
- if (tw * th > width * height) {
23050
- width = tw;
23049
+ if (tw * th > width2 * height) {
23050
+ width2 = tw;
23051
23051
  height = th;
23052
23052
  }
23053
23053
  }
@@ -23071,8 +23071,8 @@ function mp4Info(b) {
23071
23071
  };
23072
23072
  walk(0, b.length, 0);
23073
23073
  const dur = duration3 ?? mediaDuration;
23074
- const fps = width && height && dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
23075
- return { width, height, duration: dur, fps };
23074
+ const fps = width2 && height && dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
23075
+ return { width: width2, height, duration: dur, fps };
23076
23076
  }
23077
23077
  function probeMp4(b) {
23078
23078
  const info = mp4Info(b);
@@ -23100,9 +23100,9 @@ async function probeMedia(ref) {
23100
23100
  }
23101
23101
  if (!head.length) throw new ProbeError("Arquivo vazio.");
23102
23102
  if (kind === "image") {
23103
- const dim = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
23104
- if (!dim) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
23105
- return { kind, width: dim.width, height: dim.height };
23103
+ const dim2 = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
23104
+ if (!dim2) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
23105
+ return { kind, width: dim2.width, height: dim2.height };
23106
23106
  }
23107
23107
  let info = probeMp4(head);
23108
23108
  if (!info) {
@@ -23186,8 +23186,8 @@ function probeMp3(b) {
23186
23186
  function probeBuffer(b, mime) {
23187
23187
  const round = (n) => Math.round(n * 1e3) / 1e3;
23188
23188
  if (mime.startsWith("image/")) {
23189
- const dim = probePng(b) ?? probeJpeg(b) ?? probeWebp(b) ?? probeGif(b);
23190
- return dim ? { width: dim.width, height: dim.height } : null;
23189
+ const dim2 = probePng(b) ?? probeJpeg(b) ?? probeWebp(b) ?? probeGif(b);
23190
+ return dim2 ? { width: dim2.width, height: dim2.height } : null;
23191
23191
  }
23192
23192
  if (/^(video|audio)\/(mp4|quicktime|x-m4v|x-m4a|aac)$/.test(mime) || probeMp4Head(b)) {
23193
23193
  const info = mp4Info(b);
@@ -23316,14 +23316,14 @@ function registerUpscaleTools(server, client, cfg) {
23316
23316
  `"${variant}" n\xE3o \xE9 um modelo de ${isVideo ? "v\xEDdeo" : "imagem"}. Use um destes: ${allowed.join(", ")}.`
23317
23317
  );
23318
23318
  }
23319
- let width = args.source_width;
23319
+ let width2 = args.source_width;
23320
23320
  let height = args.source_height;
23321
23321
  let duration3 = args.source_duration;
23322
23322
  let fps = args.source_fps;
23323
- if (!width || !height || isVideo && !duration3) {
23323
+ if (!width2 || !height || isVideo && !duration3) {
23324
23324
  try {
23325
23325
  const probed = await probeMedia(args.file);
23326
- width = width ?? probed.width;
23326
+ width2 = width2 ?? probed.width;
23327
23327
  height = height ?? probed.height;
23328
23328
  duration3 = duration3 ?? probed.duration;
23329
23329
  fps = fps ?? probed.fps;
@@ -23358,7 +23358,7 @@ function registerUpscaleTools(server, client, cfg) {
23358
23358
  model: variant,
23359
23359
  upscale_factor: factor,
23360
23360
  prompt: args.prompt || label,
23361
- source_width: width,
23361
+ source_width: width2,
23362
23362
  source_height: height,
23363
23363
  source_duration: duration3,
23364
23364
  source_fps: fps,
@@ -23414,7 +23414,7 @@ ${footer}`);
23414
23414
  }
23415
23415
  const urls = outputUrls(status);
23416
23416
  const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
23417
- const saida = width && height ? ` \u2014 sa\xEDda ${width * factor} \xD7 ${height * factor}` : "";
23417
+ const saida = width2 && height ? ` \u2014 sa\xEDda ${width2 * factor} \xD7 ${height * factor}` : "";
23418
23418
  const semAudio = isVideo ? "\nO \xE1udio n\xE3o foi mantido (o Topaz descarta a faixa)." : "";
23419
23419
  return textResult(
23420
23420
  `\u2705 ${isVideo ? "V\xEDdeo" : "Imagem"} melhorado em ${factor}x com ${variant}${saida}.
@@ -23688,14 +23688,14 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
23688
23688
  },
23689
23689
  async (args) => {
23690
23690
  try {
23691
- let width = args.source_width;
23691
+ let width2 = args.source_width;
23692
23692
  let height = args.source_height;
23693
23693
  let duration3 = args.source_duration;
23694
23694
  let fps = args.source_fps;
23695
- if (args.file && (!width || !height)) {
23695
+ if (args.file && (!width2 || !height)) {
23696
23696
  try {
23697
23697
  const probed = await probeMedia(args.file);
23698
- width = width ?? probed.width;
23698
+ width2 = width2 ?? probed.width;
23699
23699
  height = height ?? probed.height;
23700
23700
  duration3 = duration3 ?? probed.duration;
23701
23701
  fps = fps ?? probed.fps;
@@ -23714,7 +23714,7 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
23714
23714
  quality: args.quality,
23715
23715
  upscale_factor: args.upscale_factor,
23716
23716
  model_variant: args.model_variant,
23717
- source_width: width,
23717
+ source_width: width2,
23718
23718
  source_height: height,
23719
23719
  source_duration: duration3,
23720
23720
  source_fps: fps
@@ -24397,11 +24397,14 @@ function isValidNode(key, v) {
24397
24397
  function isValidEdge(key, v) {
24398
24398
  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
24399
  }
24400
+ var MEASURED_KEY = "__measured";
24400
24401
  function checkNodePath(path) {
24401
24402
  const segments = path.split(".");
24402
24403
  if (segments.some((s) => s.length === 0)) return `caminho inv\xE1lido "${path}"`;
24403
24404
  if (segments.some((s) => FORBIDDEN_SEGMENTS.includes(s))) return `segmento proibido em "${path}"`;
24404
24405
  const [root, ...rest] = segments;
24406
+ if (root === "data" && rest[0] === MEASURED_KEY)
24407
+ return `data.${MEASURED_KEY} \xE9 o tamanho medido pela tela do Canvas: s\xF3 o Canvas grava`;
24405
24408
  if (root === "data" && rest.length >= 1) return null;
24406
24409
  if (root === "position" && rest.length === 1 && ["x", "y"].includes(rest[0]) || root === "size" && rest.length === 1 && ["w", "h"].includes(rest[0])) return null;
24407
24410
  return `s\xF3 d\xE1 para editar data.*, position.x/y e size.w/h (recebido "${path}")`;
@@ -24549,6 +24552,120 @@ function packEnvelopes(boardId3, ops, actorId, limit = MAX_OP_BYTES - 1024) {
24549
24552
  return out;
24550
24553
  }
24551
24554
 
24555
+ // src/canvas/layout.ts
24556
+ function readMeasured(node) {
24557
+ const m = node.data[MEASURED_KEY];
24558
+ if (!m || typeof m !== "object" || Array.isArray(m)) return null;
24559
+ const { w, h, at } = m;
24560
+ if (typeof w !== "number" || typeof h !== "number" || !Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null;
24561
+ return { w, h, at: typeof at === "string" ? at : null };
24562
+ }
24563
+ var SAME_WIDTH = 2;
24564
+ var WRAP_KINDS = /* @__PURE__ */ new Set(["prompt", "text", "block", "note", "label", "llm", "narration", "copy", "review", "shotlist"]);
24565
+ var SCALE_KINDS = /* @__PURE__ */ new Set(["image", "video", "media"]);
24566
+ var kindOf = (node) => typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : "";
24567
+ function heightAt(kind, baseW, baseH, newW) {
24568
+ if (Math.abs(newW - baseW) <= SAME_WIDTH) return baseH;
24569
+ if (SCALE_KINDS.has(kind) && newW > baseW) return Math.ceil(baseH * newW / baseW);
24570
+ if (WRAP_KINDS.has(kind) && newW < baseW) return Math.ceil(baseH * baseW / newW);
24571
+ return baseH;
24572
+ }
24573
+ function usedSize(node, floorW = node.size.w) {
24574
+ const kind = kindOf(node);
24575
+ const m = readMeasured(node);
24576
+ if (!m) {
24577
+ const h = Math.max(node.size.h, heightAt(kind, node.size.w, node.size.h, floorW));
24578
+ return { w: floorW, h, source: "gravado", at: null, estimated: h !== node.size.h };
24579
+ }
24580
+ const keepsWidth = Math.abs(floorW - node.size.w) <= SAME_WIDTH;
24581
+ return {
24582
+ w: keepsWidth ? Math.max(floorW, m.w) : floorW,
24583
+ h: Math.max(node.size.h, heightAt(kind, m.w, m.h, floorW)),
24584
+ source: "medido",
24585
+ at: m.at,
24586
+ estimated: Math.abs(floorW - m.w) > SAME_WIDTH
24587
+ };
24588
+ }
24589
+ function sizeLabel(node) {
24590
+ const u = usedSize(node);
24591
+ const base = `${Math.round(u.w)}\xD7${Math.round(u.h)} ${u.source}`;
24592
+ if (u.source === "medido" && u.estimated) return `${base} (medido em ${Math.round(readMeasured(node).w)} de largura: altura estimada)`;
24593
+ return base;
24594
+ }
24595
+ var DEFAULT_WIDTHS = { prompt: 1024, image: 560, video: 560, reference: 320, frame: 320, title: 1024 };
24596
+ var DEFAULT_GAP = 80;
24597
+ var FRAME_PORTS = /* @__PURE__ */ new Set(["frame"]);
24598
+ var FEEDS = /* @__PURE__ */ new Set(["prompt", "image", "video"]);
24599
+ var outOf = (doc, id) => Object.values(doc.edges).filter((e) => e.source === id);
24600
+ function roleOf(doc, node, asTitle) {
24601
+ if (asTitle) return "title";
24602
+ const kind = kindOf(node);
24603
+ if (kind === "prompt" || kind === "video") return kind;
24604
+ if (kind !== "media" && kind !== "image") return "other";
24605
+ const feedsFrame = outOf(doc, node.id).some((e) => FRAME_PORTS.has(e.targetPort) && doc.nodes[e.target] && kindOf(doc.nodes[e.target]) === "video");
24606
+ if (feedsFrame) return "frame";
24607
+ return kind === "media" ? "reference" : "image";
24608
+ }
24609
+ function hangsRight(doc, node) {
24610
+ const kind = kindOf(node);
24611
+ if (kind !== "media" && kind !== "image") return false;
24612
+ const out = outOf(doc, node.id);
24613
+ if (!out.length) return kind === "media";
24614
+ return out.some((e) => doc.nodes[e.target] && FEEDS.has(kindOf(doc.nodes[e.target])));
24615
+ }
24616
+ 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;
24617
+ function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDTHS) {
24618
+ const placed = [];
24619
+ const floorOf = (node, role) => role === "other" ? node.size.w : widths[role];
24620
+ const place = (id, row, asTitle, x, y2, alignRight) => {
24621
+ const node = doc.nodes[id];
24622
+ const role = roleOf(doc, node, asTitle);
24623
+ const floorW = floorOf(node, role);
24624
+ const u = usedSize(node, floorW);
24625
+ const w = Math.ceil(u.w);
24626
+ const h = Math.ceil(u.h);
24627
+ const px = alignRight !== void 0 ? alignRight - w : x;
24628
+ const p = { id, row, role, x: Math.round(px), y: Math.round(y2), w, h, source: u.source, estimated: u.estimated, floorW };
24629
+ placed.push(p);
24630
+ return p;
24631
+ };
24632
+ let y = origin.y;
24633
+ rows.forEach((row, r) => {
24634
+ if (row.title) {
24635
+ const t = place(row.title, r, true, origin.x, y);
24636
+ y = t.y + t.h + Math.round(gap / 2);
24637
+ }
24638
+ const top = y;
24639
+ 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)));
24640
+ let leftEnd = top;
24641
+ row.left.forEach((id, i) => {
24642
+ const right2 = hangsRight(doc, doc.nodes[id]);
24643
+ const p = place(id, r, false, origin.x, i === 0 ? top : leftEnd + gap, right2 ? origin.x + leftW : void 0);
24644
+ leftEnd = p.y + p.h;
24645
+ });
24646
+ const rightX = origin.x + (row.left.length ? leftW + gap : 0);
24647
+ let rightEnd = top;
24648
+ row.right.forEach((id, i) => {
24649
+ const p = place(id, r, false, rightX, i === 0 ? top : rightEnd + gap);
24650
+ rightEnd = p.y + p.h;
24651
+ });
24652
+ y = Math.max(leftEnd, rightEnd, top) + gap * 2;
24653
+ });
24654
+ const inner = [];
24655
+ for (let i = 0; i < placed.length; i++) {
24656
+ for (let j = i + 1; j < placed.length; j++) if (overlap(placed[i], placed[j])) inner.push([placed[i].id, placed[j].id]);
24657
+ }
24658
+ const ids = new Set(placed.map((p) => p.id));
24659
+ 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) }));
24660
+ const outer = [];
24661
+ for (const p of placed) for (const o of others) if (overlap(p, o)) outer.push([p.id, o.id]);
24662
+ const left = Math.min(origin.x, ...placed.map((p) => p.x));
24663
+ const right = Math.max(origin.x, ...placed.map((p) => p.x + p.w));
24664
+ const below = others.filter((o) => o.x < right && left < o.x + o.w).map((o) => o.y + o.h);
24665
+ const freeOrigin = { x: origin.x, y: below.length ? Math.max(origin.y, Math.ceil(Math.max(...below) + gap * 2)) : origin.y };
24666
+ return { placed, inner, outer, freeOrigin };
24667
+ }
24668
+
24552
24669
  // src/canvas/summary.ts
24553
24670
  var MAX_TEXT = 160;
24554
24671
  var MAX_BLOCKS = 40;
@@ -24571,8 +24688,14 @@ var HEAVY_KEYS = /* @__PURE__ */ new Set([
24571
24688
  "settings",
24572
24689
  "enabledKeys",
24573
24690
  "checks",
24574
- "versions"
24691
+ "versions",
24692
+ MEASURED_KEY
24575
24693
  ]);
24694
+ function visibleData(data) {
24695
+ if (!Object.hasOwn(data, MEASURED_KEY)) return data;
24696
+ const { [MEASURED_KEY]: _measured, ...rest } = data;
24697
+ return rest;
24698
+ }
24576
24699
  var NAME_KEYS = /* @__PURE__ */ new Set([KIND_FIELD, "label", "nodeTitle"]);
24577
24700
  function oneLine(text, max = MAX_TEXT) {
24578
24701
  const flat = text.replace(/\s+/g, " ").trim();
@@ -24625,7 +24748,7 @@ function rowsLines(rows) {
24625
24748
  function nodeLines(node, run) {
24626
24749
  const kind = nodeKind(node);
24627
24750
  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)})`;
24751
+ 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
24752
  const facts = [];
24630
24753
  const extra = [];
24631
24754
  const d = node.data;
@@ -24690,7 +24813,7 @@ function boardSummary(snap) {
24690
24813
  lines.push(`IA Lab: vinculado${label ? ` a ${String(label)}` : ""}.`);
24691
24814
  }
24692
24815
  if (doc.meta?.guide) lines.push(`Guia: ${oneLine(doc.meta.guide)}`);
24693
- lines.push("", "N\xF3s:");
24816
+ lines.push("", "N\xF3s (tamanho: medido = como est\xE1 na tela; gravado = piso, o cart\xE3o pode ser maior):");
24694
24817
  if (!nodes.length) lines.push("(vazio)");
24695
24818
  for (const n of nodes) lines.push(...nodeLines(n, runs.get(n.id)));
24696
24819
  lines.push("", "Liga\xE7\xF5es:");
@@ -24734,12 +24857,28 @@ function nodeDetail(node) {
24734
24857
  }
24735
24858
  return v;
24736
24859
  };
24737
- const json2 = JSON.stringify(strip(node.data, 0, ""));
24860
+ const json2 = JSON.stringify(strip(visibleData(node.data), 0, ""));
24738
24861
  const note = cuts ? `
24739
24862
  (${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)}"
24863
+ return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}" \xB7 ${sizeLabel(node)}
24741
24864
  ${json2}${note}`;
24742
24865
  }
24866
+ var isMeasureNoise = (op) => op.type === "node.set" && typeof op.path === "string" && (op.path === `data.${MEASURED_KEY}` || op.path.startsWith(`data.${MEASURED_KEY}.`));
24867
+ function withoutMeasureNoise(envelopes) {
24868
+ const clean = (op) => {
24869
+ if (op.type === "batch" && Array.isArray(op.ops)) {
24870
+ const ops = op.ops.map((o) => clean(o)).filter((o) => o !== null);
24871
+ return ops.length ? { ...op, ops } : null;
24872
+ }
24873
+ return isMeasureNoise(op) ? null : op;
24874
+ };
24875
+ const out = [];
24876
+ for (const e of envelopes) {
24877
+ const op = clean(e.op);
24878
+ if (op) out.push({ ...e, op });
24879
+ }
24880
+ return out;
24881
+ }
24743
24882
  function changesSince(envelopes, doc) {
24744
24883
  const count = /* @__PURE__ */ new Map();
24745
24884
  for (const e of envelopes) {
@@ -25049,6 +25188,158 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25049
25188
  );
25050
25189
  }
25051
25190
 
25191
+ // src/tools/canvas-layout.ts
25192
+ var nodeIdArg = external_exports.string().min(1).max(128);
25193
+ var width = external_exports.number().finite().min(40).max(2e4);
25194
+ var ROLE_TEXT = {
25195
+ title: "t\xEDtulo",
25196
+ prompt: "prompt",
25197
+ image: "imagem",
25198
+ video: "v\xEDdeo",
25199
+ reference: "ref",
25200
+ frame: "frame",
25201
+ other: "outro"
25202
+ };
25203
+ function checkRows(doc, rows) {
25204
+ const seen = /* @__PURE__ */ new Set();
25205
+ for (const [i, row] of rows.entries()) {
25206
+ const ids = [...row.title ? [row.title] : [], ...row.left, ...row.right];
25207
+ if (!ids.length) throw new CanvasToolError(`rows[${i}] est\xE1 vazia: informe title, left ou right.`);
25208
+ for (const id of ids) {
25209
+ const node = doc.nodes[id];
25210
+ if (!node) throw new CanvasToolError(`rows[${i}]: o n\xF3 ${id} n\xE3o existe no board. Releia com canvas_read_board.`);
25211
+ if (node.data[KIND_FIELD] === "section") throw new CanvasToolError(`rows[${i}]: ${id} \xE9 uma Se\xE7\xE3o (fundo), n\xE3o entra no arranjo.`);
25212
+ if (seen.has(id)) throw new CanvasToolError(`O n\xF3 ${id} aparece duas vezes nas linhas: cada n\xF3 entra uma vez s\xF3.`);
25213
+ seen.add(id);
25214
+ }
25215
+ }
25216
+ }
25217
+ function layoutOps(doc, a) {
25218
+ const ops = [];
25219
+ for (const p of a.placed) {
25220
+ const node = doc.nodes[p.id];
25221
+ const set = (path, now, next) => {
25222
+ if (now !== next) ops.push({ type: "node.set", nodeId: p.id, path, value: setValue(next) });
25223
+ };
25224
+ set("position.x", node.position.x, p.x);
25225
+ set("position.y", node.position.y, p.y);
25226
+ set("size.w", node.size.w, p.floorW);
25227
+ }
25228
+ return ops;
25229
+ }
25230
+ function report(doc, a) {
25231
+ const name = (id) => `"${oneLine(nodeName(doc.nodes[id]), 36)}"`;
25232
+ const lines = [];
25233
+ const unmeasured = a.placed.filter((p) => p.source === "gravado");
25234
+ const estimated = a.placed.filter((p) => p.source === "medido" && p.estimated);
25235
+ if (unmeasured.length || estimated.length) {
25236
+ lines.push("A conferir (a altura usada pode n\xE3o ser a da tela):");
25237
+ if (unmeasured.length) {
25238
+ lines.push(
25239
+ `- 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(", ")}`
25240
+ );
25241
+ }
25242
+ if (estimated.length) {
25243
+ const how = estimated.map((p) => `${name(p.id)} (medido com ${Math.round(readMeasured(doc.nodes[p.id]).w)} de largura)`);
25244
+ lines.push(`- altura estimada com folga porque a largura mudou: ${how.join(", ")}`);
25245
+ }
25246
+ lines.push(
25247
+ "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."
25248
+ );
25249
+ } else {
25250
+ lines.push("Todos os n\xF3s arrumados t\xEAm medida da tela.");
25251
+ }
25252
+ const pairs = (list) => list.map(([x, y]) => `${name(x)} \xD7 ${name(y)}`).join("; ");
25253
+ lines.push(
25254
+ 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."
25255
+ );
25256
+ lines.push('Caixas ([papel] id "nome" @(x,y) largura\xD7altura usada, origem da altura):');
25257
+ const rows = /* @__PURE__ */ new Map();
25258
+ for (const p of a.placed) {
25259
+ 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" : ""}`;
25260
+ rows.set(p.row, [...rows.get(p.row) ?? [], item]);
25261
+ }
25262
+ for (const [r, items] of rows) lines.push(`- linha ${r + 1}: ${items.join(" \xB7 ")}`);
25263
+ const resized = a.placed.filter((p) => p.floorW !== doc.nodes[p.id].size.w);
25264
+ if (resized.length) lines.push(`Largura (size.w): ${resized.map((p) => `${name(p.id)} ${doc.nodes[p.id].size.w}\u2192${p.floorW}`).join(", ")}.`);
25265
+ const titles = a.placed.filter((p) => p.role === "title" && doc.nodes[p.id].data[KIND_FIELD] === "label" && doc.nodes[p.id].data.size !== "titulo");
25266
+ if (titles.length) {
25267
+ lines.push(
25268
+ `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).`
25269
+ );
25270
+ }
25271
+ return lines.join("\n");
25272
+ }
25273
+ function registerCanvasLayoutTools(server, deps) {
25274
+ const { api, run, edit } = deps;
25275
+ server.registerTool(
25276
+ "canvas_arrange_rows",
25277
+ {
25278
+ title: "Canvas: arrumar em linhas",
25279
+ 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.",
25280
+ inputSchema: {
25281
+ board_id: external_exports.string().uuid(),
25282
+ origin: external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() }).describe("Canto de cima \xE0 esquerda da \xE1rea."),
25283
+ rows: external_exports.array(
25284
+ external_exports.object({
25285
+ title: nodeIdArg.optional().describe("N\xF3 do t\xEDtulo da linha (Texto livre)."),
25286
+ left: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da esquerda, de cima para baixo: prompt, depois a refer\xEAncia."),
25287
+ right: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da direita: gerador(es).")
25288
+ }).strict()
25289
+ ).min(1).max(50),
25290
+ gap: external_exports.number().finite().min(0).max(1e3).optional().describe(`Espa\xE7o entre cart\xF5es (padr\xE3o ${DEFAULT_GAP}); o dobro entre linhas.`),
25291
+ widths: external_exports.object({
25292
+ prompt: width.optional(),
25293
+ image: width.optional(),
25294
+ video: width.optional(),
25295
+ generator: width.optional().describe("Atalho: image e video juntos."),
25296
+ reference: width.optional(),
25297
+ frame: width.optional(),
25298
+ title: width.optional()
25299
+ }).strict().optional().describe("Troca larguras da conven\xE7\xE3o (prompt 1024, image 560, video 560, reference 320, frame 320, title 1024)."),
25300
+ dry_run: external_exports.boolean().optional().describe("true: s\xF3 calcula e mostra, sem gravar.")
25301
+ },
25302
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
25303
+ },
25304
+ async (args) => run(async () => {
25305
+ const rows = args.rows.map((r) => ({ title: r.title, left: r.left ?? [], right: r.right ?? [] }));
25306
+ const { generator, ...own } = args.widths ?? {};
25307
+ const widths = { ...DEFAULT_WIDTHS, ...generator ? { image: generator, video: generator } : {}, ...own };
25308
+ const gap = args.gap ?? DEFAULT_GAP;
25309
+ const plan = (doc) => {
25310
+ checkRows(doc, rows);
25311
+ const a = arrangeRows(doc, rows, args.origin, gap, widths);
25312
+ return { a, ops: layoutOps(doc, a), text: report(doc, a) };
25313
+ };
25314
+ const count = (ops) => new Set(ops.map((o) => o.type === "node.set" ? o.nodeId : "")).size;
25315
+ 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.`;
25316
+ if (args.dry_run) {
25317
+ const { a, ops, text: text2 } = plan((await api.snapshot(args.board_id)).doc);
25318
+ const head = `Pr\xE9via, nada gravado: ${ops.length ? `${count(ops)} n\xF3(s) mudariam de lugar ou largura` : "tudo j\xE1 est\xE1 assim"}.`;
25319
+ return [head, ...a.outer.length ? [blocked(a)] : [], text2].join("\n");
25320
+ }
25321
+ let text = "";
25322
+ const done = await edit(
25323
+ args.board_id,
25324
+ async (doc) => {
25325
+ const p = plan(doc);
25326
+ text = p.text;
25327
+ if (p.a.outer.length) throw new CanvasToolError(`${blocked(p.a)}
25328
+ ${p.text}`);
25329
+ return {
25330
+ ops: p.ops,
25331
+ targets: [],
25332
+ done: `Arranjo: ${rows.length} linha(s), ${p.a.placed.length} n\xF3(s), ${count(p.ops)} mudaram de lugar ou largura`
25333
+ };
25334
+ },
25335
+ { split: true, layoutOnly: true }
25336
+ );
25337
+ return `${done}
25338
+ ${text}`;
25339
+ })
25340
+ );
25341
+ }
25342
+
25052
25343
  // src/canvas/upload.ts
25053
25344
  import { randomUUID as randomUUID2 } from "crypto";
25054
25345
  import { basename as basename2, extname as extname3 } from "path";
@@ -25257,6 +25548,137 @@ ${payload(u)}`
25257
25548
  );
25258
25549
  }
25259
25550
 
25551
+ // src/canvas/mentions.ts
25552
+ var VIA_MENTION = "viaMention";
25553
+ var PROMPT_REFS_INPUT = "refs";
25554
+ var PROMPT_MENTION_REFS_INPUT = "mentionRefs";
25555
+ var MENTION_KEYS = ["template", "fields", "mentions"];
25556
+ var VARIABLE_SOURCE = String.raw`\{\{[ \t]*([\p{L}\p{N}_-]+)[ \t]*\}\}`;
25557
+ function extractVariables(text) {
25558
+ const names = /* @__PURE__ */ new Set();
25559
+ for (const m of text.matchAll(new RegExp(VARIABLE_SOURCE, "gu"))) if (m[1]) names.add(m[1]);
25560
+ return [...names];
25561
+ }
25562
+ function allVariableNames(data) {
25563
+ const names = new Set(extractVariables(typeof data.template === "string" ? data.template : ""));
25564
+ const fields = data.fields;
25565
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
25566
+ for (const [id, text] of Object.entries(fields)) {
25567
+ if (id !== "" && typeof text === "string" && text !== "") for (const n of extractVariables(text)) names.add(n);
25568
+ }
25569
+ }
25570
+ return [...names];
25571
+ }
25572
+ function normalizeMentions(value) {
25573
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
25574
+ const out = {};
25575
+ for (const [name, entry] of Object.entries(value)) {
25576
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
25577
+ const { node, port } = entry;
25578
+ if (name.trim() !== "" && typeof node === "string" && node !== "" && typeof port === "string" && port !== "") {
25579
+ out[name] = { node, port };
25580
+ }
25581
+ }
25582
+ return out;
25583
+ }
25584
+ function mentionEdgeId(e) {
25585
+ return `mention:${e.source}:${e.sourcePort}>${e.target}:${e.targetPort}`;
25586
+ }
25587
+ var edgeKey = (e) => `${e.source}\0${e.sourcePort}\0${e.target}\0${e.targetPort}`;
25588
+ var isViaMention = (e) => e.data[VIA_MENTION] === true;
25589
+ function reachability(doc) {
25590
+ const adj = /* @__PURE__ */ new Map();
25591
+ const link = (s, t) => adj.set(s, (adj.get(s) ?? /* @__PURE__ */ new Set()).add(t));
25592
+ for (const e of Object.values(doc.edges)) link(e.source, e.target);
25593
+ const reaches = (from, to) => {
25594
+ const seen = /* @__PURE__ */ new Set();
25595
+ const stack = [from];
25596
+ while (stack.length) {
25597
+ for (const next of adj.get(stack.pop()) ?? []) {
25598
+ if (next === to) return true;
25599
+ if (!seen.has(next)) {
25600
+ seen.add(next);
25601
+ stack.push(next);
25602
+ }
25603
+ }
25604
+ }
25605
+ return false;
25606
+ };
25607
+ return { wouldCycle: (s, t) => s === t || reaches(t, s), accept: link };
25608
+ }
25609
+ function originHasPort(doc, origin) {
25610
+ const node = doc.nodes[origin.node];
25611
+ if (!node) return false;
25612
+ const kind = typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : null;
25613
+ if (!kind || !KINDS[kind]) return false;
25614
+ return knownPort(kind, "out", origin.port) !== false;
25615
+ }
25616
+ function mentionEdgeOps(doc, promptIds, actorId, now = Date.now()) {
25617
+ const out = { ops: [], added: [], removed: [], skipped: [] };
25618
+ const reach = reachability(doc);
25619
+ const edges = Object.values(doc.edges);
25620
+ const desired = /* @__PURE__ */ new Map();
25621
+ const managed = [];
25622
+ for (const id of [...new Set(promptIds)]) {
25623
+ const prompt = doc.nodes[id];
25624
+ if (!prompt || prompt.data[KIND_FIELD] !== "prompt") continue;
25625
+ const into = edges.filter((e) => e.target === id);
25626
+ const vars = new Set(allVariableNames(prompt.data));
25627
+ const mentions2 = normalizeMentions(prompt.data.mentions);
25628
+ const mentionPorts = new Set(into.filter(isViaMention).map((e) => e.targetPort));
25629
+ managed.push(...into.filter((e) => isViaMention(e) && e.targetPort !== PROMPT_REFS_INPUT && e.targetPort !== PROMPT_MENTION_REFS_INPUT));
25630
+ for (const port of /* @__PURE__ */ new Set([...vars, ...mentionPorts])) {
25631
+ if (port === PROMPT_REFS_INPUT || port === PROMPT_MENTION_REFS_INPUT) continue;
25632
+ const origin = vars.has(port) ? mentions2[port] : void 0;
25633
+ if (!origin) continue;
25634
+ if (into.some((e) => e.targetPort === port && !isViaMention(e))) {
25635
+ out.skipped.push(`{{${port}}}: a porta j\xE1 recebe uma liga\xE7\xE3o feita \xE0 m\xE3o`);
25636
+ continue;
25637
+ }
25638
+ if (!doc.nodes[origin.node]) {
25639
+ out.skipped.push(`{{${port}}}: o n\xF3 de origem ${origin.node} n\xE3o existe`);
25640
+ continue;
25641
+ }
25642
+ if (!originHasPort(doc, origin)) {
25643
+ out.skipped.push(`{{${port}}}: a porta "${origin.port}" n\xE3o existe em ${origin.node}`);
25644
+ continue;
25645
+ }
25646
+ if (reach.wouldCycle(origin.node, id)) {
25647
+ out.skipped.push(`{{${port}}}: a liga\xE7\xE3o fecharia um ciclo`);
25648
+ continue;
25649
+ }
25650
+ const link = { source: origin.node, sourcePort: origin.port, target: id, targetPort: port };
25651
+ desired.set(edgeKey(link), link);
25652
+ reach.accept(origin.node, id);
25653
+ }
25654
+ }
25655
+ for (const e of managed) {
25656
+ if (desired.has(edgeKey(e))) continue;
25657
+ out.ops.push({ type: "edge.remove", edgeId: e.id });
25658
+ out.removed.push(e);
25659
+ }
25660
+ for (const [key, link] of desired) {
25661
+ if (edges.some((e) => edgeKey(e) === key)) continue;
25662
+ const edgeId = mentionEdgeId(link);
25663
+ out.ops.push({
25664
+ type: "edge.add",
25665
+ edgeId,
25666
+ edge: { id: edgeId, ...link, data: { [VIA_MENTION]: true }, createdBy: actorId, createdAt: now }
25667
+ });
25668
+ out.added.push(link);
25669
+ }
25670
+ return out;
25671
+ }
25672
+ function describeMentionSync(sync, name) {
25673
+ const parts = [];
25674
+ if (sync.added.length) {
25675
+ parts.push(`liga\xE7\xE3o de men\xE7\xE3o criada: ${sync.added.map((l) => `${name(l.source)}.${l.sourcePort} \u2192 {{${l.targetPort}}}`).join(", ")}`);
25676
+ }
25677
+ if (sync.removed.length) parts.push(`liga\xE7\xE3o de men\xE7\xE3o removida: ${sync.removed.map((e) => `{{${e.targetPort}}}`).join(", ")}`);
25678
+ if (sync.skipped.length) parts.push(`men\xE7\xE3o sem liga\xE7\xE3o: ${sync.skipped.join("; ")}`);
25679
+ return parts.length ? ` ${parts.join(". ")}.` : "";
25680
+ }
25681
+
25260
25682
  // src/tools/canvas.ts
25261
25683
  var REJECT_TEXT = {
25262
25684
  "node-missing": "o n\xF3 n\xE3o existe mais (algu\xE9m apagou)",
@@ -25265,6 +25687,7 @@ var REJECT_TEXT = {
25265
25687
  "too-large": "opera\xE7\xE3o grande demais (64 KB) ou com m\xEDdia embutida"
25266
25688
  };
25267
25689
  var READ = "Exige canvas_read_board nesta sess\xE3o (e de novo depois de uma recusa). ";
25690
+ 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
25691
  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
25692
  function dataSegments(path) {
25270
25693
  const p = path?.trim() ?? "";
@@ -25283,8 +25706,26 @@ function legacySequenceGuard(node, key) {
25283
25706
  var SECTION_Z = -1e3;
25284
25707
  var GAP = 80;
25285
25708
  var boardId2 = external_exports.string().uuid().describe("Id (uuid) do board, como sai em canvas_list_boards.");
25286
- var nodeIdArg = external_exports.string().min(1).max(128);
25709
+ var nodeIdArg2 = external_exports.string().min(1).max(128);
25287
25710
  var point = external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() });
25711
+ var LABEL_TEXT_SIZES = ["titulo", "subtitulo", "corpo"];
25712
+ var dim = external_exports.number().finite().min(16).max(2e4);
25713
+ var sizeArg = external_exports.union([external_exports.object({ w: dim.optional(), h: dim.optional() }).strict(), external_exports.enum(LABEL_TEXT_SIZES)]).describe(
25714
+ '{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.'
25715
+ );
25716
+ var isLayoutOp = (op) => op.type === "batch" ? op.ops.every(isLayoutOp) : op.type === "node.set" && /^(position\.[xy]|size\.[wh])$/.test(op.path);
25717
+ function textSizeFor(kind, size) {
25718
+ if (kind !== "label") {
25719
+ throw new CanvasToolError(
25720
+ `size "${size}" \xE9 o tamanho do texto do Texto livre (label); para ${kind ?? "este n\xF3"} use size {w, h}.`
25721
+ );
25722
+ }
25723
+ return size;
25724
+ }
25725
+ function boxOf(size) {
25726
+ if (size.w === void 0 && size.h === void 0) throw new CanvasToolError("size {w, h}: informe w, h ou os dois.");
25727
+ return size;
25728
+ }
25288
25729
  function registerCanvasTools(server, client, cfg) {
25289
25730
  const api = new CanvasApi(client, cfg);
25290
25731
  const seen = /* @__PURE__ */ new Map();
@@ -25308,7 +25749,7 @@ function registerCanvasTools(server, client, cfg) {
25308
25749
  }
25309
25750
  async function edit(id, build, opts = {}) {
25310
25751
  const last = seen.get(id);
25311
- if (last === void 0) {
25752
+ if (last === void 0 && !opts.layoutOnly) {
25312
25753
  throw new CanvasToolError("Leia o board com canvas_read_board antes de editar (a leitura vale para a sess\xE3o).");
25313
25754
  }
25314
25755
  const snap = await api.snapshot(id);
@@ -25316,10 +25757,11 @@ function registerCanvasTools(server, client, cfg) {
25316
25757
  const doc = snap.doc;
25317
25758
  const { ops, done, targets } = await build(doc, snap);
25318
25759
  if (!ops.length) return `Nada a mudar. ${done}`;
25760
+ if (opts.layoutOnly && !ops.every(isLayoutOp)) throw new Error("layout_only com opera\xE7\xE3o fora de posi\xE7\xE3o/tamanho");
25319
25761
  let note = "";
25320
- if (snap.seq > last) {
25762
+ if (!opts.layoutOnly && last !== void 0 && snap.seq > last) {
25321
25763
  const since = await api.opsAfter(id, last);
25322
- const theirs = since?.filter((e) => e.clientId !== CLIENT_ID) ?? null;
25764
+ const theirs = since === null ? null : withoutMeasureNoise(since.filter((e) => e.clientId !== CLIENT_ID));
25323
25765
  if (theirs === null) {
25324
25766
  seen.delete(id);
25325
25767
  throw new CanvasToolError(
@@ -25337,21 +25779,21 @@ function registerCanvasTools(server, client, cfg) {
25337
25779
  if (theirs.length) note = `
25338
25780
  Aviso: ${changesSince(theirs, doc)}.`;
25339
25781
  }
25340
- if (opts.split) return commitSplit(id, ops, done, note);
25782
+ if (opts.split) return commitSplit(id, ops, done, note, !!opts.layoutOnly);
25341
25783
  const env = envelope(id, batchOf(ops), await api.me());
25342
25784
  const bad = checkEnvelope(env);
25343
25785
  if (bad) throw new CanvasToolError(`Edi\xE7\xE3o recusada antes de enviar: ${bad}.`);
25344
25786
  const res = await api.commit(id, [env]);
25345
25787
  const outcome = res.outcomes.find((o) => o.opId === env.id);
25346
25788
  if (!outcome || "rejected" in outcome) {
25347
- seen.delete(id);
25789
+ if (!opts.layoutOnly) seen.delete(id);
25348
25790
  const why = outcome && "rejected" in outcome ? REJECT_TEXT[outcome.rejected] ?? outcome.rejected : "sem resposta do servidor";
25349
25791
  throw new CanvasToolError(`O servidor recusou a edi\xE7\xE3o: ${why}. Nada foi aplicado. Releia com canvas_read_board.`);
25350
25792
  }
25351
- seen.set(id, res.seq);
25793
+ if (!opts.layoutOnly) seen.set(id, res.seq);
25352
25794
  return `\u2705 ${done} (seq ${res.seq}).${note}`;
25353
25795
  }
25354
- async function commitSplit(id, ops, done, note) {
25796
+ async function commitSplit(id, ops, done, note, layoutOnly = false) {
25355
25797
  let envs;
25356
25798
  try {
25357
25799
  envs = packEnvelopes(id, ops, await api.me());
@@ -25373,7 +25815,8 @@ Aviso: ${changesSince(theirs, doc)}.`;
25373
25815
  for (const o of res.outcomes) outcomes.set(o.opId, o);
25374
25816
  last = res.seq;
25375
25817
  }
25376
- const pathsOf = (op) => op.type === "batch" ? op.ops.flatMap(pathsOf) : op.type === "node.set" ? [op.path.replace(/^data\./, "")] : [op.type];
25818
+ const setNodes = new Set(ops.flatMap((op) => op.type === "batch" ? op.ops : [op]).flatMap((op) => op.type === "node.set" ? [op.nodeId] : []));
25819
+ 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
25820
  let failed = 0;
25378
25821
  const lines = envs.map((env, i) => {
25379
25822
  const paths = pathsOf(env.op);
@@ -25384,14 +25827,14 @@ Aviso: ${changesSince(theirs, doc)}.`;
25384
25827
  return `- envelope ${i + 1}/${envs.length}: ${paths.length} caminho(s) (${span}), ${kb} KB \u2192 ${result}`;
25385
25828
  });
25386
25829
  if (failed) {
25387
- seen.delete(id);
25830
+ if (!layoutOnly) seen.delete(id);
25388
25831
  throw new CanvasToolError(
25389
25832
  `${done}: ${envs.length - failed} de ${envs.length} envelope(s) entraram, ${failed} recusado(s).
25390
25833
  ${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.` + note
25834
+ ` + (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
25835
  );
25393
25836
  }
25394
- if (last !== null) seen.set(id, last);
25837
+ if (last !== null && !layoutOnly) seen.set(id, last);
25395
25838
  return `\u2705 ${done} em ${envs.length} envelope(s) (seq at\xE9 ${last}).
25396
25839
  ${lines.join("\n")}${note}`;
25397
25840
  }
@@ -25445,11 +25888,53 @@ ${lines.join("\n")}${note}`;
25445
25888
  const out = {};
25446
25889
  for (const [k, v] of Object.entries(raw ?? {})) {
25447
25890
  if (k === KIND_FIELD) throw new CanvasToolError("data.kind n\xE3o muda: apague o n\xF3 e crie outro do tipo certo.");
25891
+ if (k === MEASURED_KEY) {
25892
+ 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}.`);
25893
+ }
25448
25894
  if (!isJson(v)) throw new CanvasToolError(`data.${k} n\xE3o \xE9 JSON v\xE1lido.`);
25449
25895
  out[k] = v;
25450
25896
  }
25451
25897
  return out;
25452
25898
  }
25899
+ function changeOps(node, change) {
25900
+ const ops = [];
25901
+ const patch = parseData(change.data);
25902
+ for (const key of Object.keys(patch)) legacySequenceGuard(node, key);
25903
+ if (change.label) patch.label = change.label;
25904
+ let box;
25905
+ if (typeof change.size === "string") patch.size = textSizeFor(nodeKind(node), change.size);
25906
+ else if (change.size) box = boxOf(change.size);
25907
+ const data = { ...node.data };
25908
+ for (const [k, v] of Object.entries(patch)) {
25909
+ const path = `data.${k}`;
25910
+ if (v === null) {
25911
+ if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
25912
+ delete data[k];
25913
+ } else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
25914
+ ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
25915
+ data[k] = v;
25916
+ }
25917
+ }
25918
+ const set = (path, now, next) => {
25919
+ if (next !== void 0 && next !== now) ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(next) });
25920
+ };
25921
+ set("position.x", node.position.x, change.position?.x);
25922
+ set("position.y", node.position.y, change.position?.y);
25923
+ set("size.w", node.size.w, box?.w);
25924
+ set("size.h", node.size.h, box?.h);
25925
+ const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "");
25926
+ return { ops, keys, data, dataKeys: Object.keys(patch) };
25927
+ }
25928
+ async function mentionOps(doc, changed, newEdges = []) {
25929
+ const prompts = changed.filter((c) => nodeKind(c.node) === "prompt" && c.keys.some((k) => MENTION_KEYS.includes(k)));
25930
+ if (!prompts.length) return { ops: [], note: "" };
25931
+ const after = { ...doc, nodes: { ...doc.nodes }, edges: { ...doc.edges } };
25932
+ for (const c of changed) after.nodes[c.node.id] = { ...c.node, data: c.data };
25933
+ for (const e of newEdges) after.edges[e.id] = e;
25934
+ const sync = mentionEdgeOps(after, prompts.map((c) => c.node.id), await api.me());
25935
+ const name = (id) => after.nodes[id] ? `"${oneLine(nodeName(after.nodes[id]), 40)}"` : id;
25936
+ return { ops: sync.ops, note: describeMentionSync(sync, name) };
25937
+ }
25453
25938
  server.registerTool(
25454
25939
  "canvas_list_boards",
25455
25940
  {
@@ -25468,10 +25953,10 @@ ${lines.join("\n")}${note}`;
25468
25953
  "canvas_read_board",
25469
25954
  {
25470
25955
  title: "Canvas: ler board",
25471
- description: "L\xEA um board: n\xF3s (id, tipo, nome, campos principais), liga\xE7\xF5es e v\xEDnculo com o IA Lab, sem m\xEDdia. 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.",
25956
+ 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
25957
  inputSchema: {
25473
25958
  board_id: boardId2,
25474
- node_ids: external_exports.array(nodeIdArg).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
25959
+ node_ids: external_exports.array(nodeIdArg2).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
25475
25960
  },
25476
25961
  annotations: { readOnlyHint: true, openWorldHint: false }
25477
25962
  },
@@ -25493,7 +25978,7 @@ ${lines.join("\n")}${note}`;
25493
25978
  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
25979
  inputSchema: {
25495
25980
  board_id: boardId2,
25496
- node_id: nodeIdArg,
25981
+ node_id: nodeIdArg2,
25497
25982
  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
25983
  offset: external_exports.number().int().min(0).optional().describe("In\xEDcio da parte, em caracteres (padr\xE3o 0)."),
25499
25984
  max_chars: external_exports.number().int().min(1e3).max(6e4).optional().describe("Tamanho da parte (padr\xE3o 40000).")
@@ -25504,8 +25989,11 @@ ${lines.join("\n")}${note}`;
25504
25989
  const snap = await api.snapshot(board_id);
25505
25990
  const node = snap.doc.nodes[node_id];
25506
25991
  if (!node) throw new CanvasToolError(`N\xF3 ${node_id} n\xE3o existe no board. Releia com canvas_read_board.`);
25507
- let value = node.data;
25992
+ let value = visibleData(node.data);
25508
25993
  let segments = dataSegments(path);
25994
+ if (segments[0] === MEASURED_KEY) {
25995
+ throw new CanvasToolError(`data.${MEASURED_KEY} \xE9 interno do Canvas (tamanho medido da tela): ele sai no cabe\xE7alho desta leitura.`);
25996
+ }
25509
25997
  let which = "";
25510
25998
  if (!path?.trim() && nodeKind(node) === "montage") {
25511
25999
  if (node.data.timeline !== void 0) {
@@ -25522,9 +26010,9 @@ ${lines.join("\n")}${note}`;
25522
26010
  const here = value;
25523
26011
  value = Array.isArray(here) ? here[Number(seg)] : here && typeof here === "object" ? here[seg] : void 0;
25524
26012
  if (value === void 0) {
25525
- const at = segments.slice(0, i).join(".");
26013
+ const at2 = segments.slice(0, i).join(".");
25526
26014
  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${at ? `.${at}` : ""}. Existe: ${keys || "\u2014"}.`);
26015
+ throw new CanvasToolError(`"${seg}" n\xE3o existe em data${at2 ? `.${at2}` : ""}. Existe: ${keys || "\u2014"}.`);
25528
26016
  }
25529
26017
  }
25530
26018
  const json2 = JSON.stringify(value);
@@ -25534,7 +26022,8 @@ ${lines.join("\n")}${note}`;
25534
26022
  const to = Math.min(from + size, total);
25535
26023
  const hash = createHash("sha256").update(json2).digest("hex").slice(0, 12);
25536
26024
  const where = `data${segments.length ? `.${segments.join(".")}` : ""}`;
25537
- const head = `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" \xB7 board seq ${snap.seq} \xB7 ${where}${which}: ${total} caracteres (JSON compacto) \xB7 sha256 ${hash}`;
26025
+ const at = readMeasured(node)?.at;
26026
+ 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
26027
  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
26028
  return `${head}
25540
26029
  ${part}
@@ -25595,15 +26084,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25595
26084
  "canvas_add_node",
25596
26085
  {
25597
26086
  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,
26087
+ 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
26088
  inputSchema: {
25600
26089
  board_id: boardId2,
25601
26090
  kind: external_exports.string().min(1).max(40),
25602
26091
  label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do cart\xE3o (data.label)."),
25603
26092
  data: external_exports.record(external_exports.unknown()).optional().describe("Campos do data. Ex.: narration {blocks:[{id,text}]}, text {text}, prompt {template}."),
25604
26093
  position: point.optional(),
25605
- inputs: external_exports.array(external_exports.object({ from: nodeIdArg, 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."),
25606
- outputs: external_exports.array(external_exports.object({ from_port: external_exports.string().min(1), to: nodeIdArg, 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.")
26094
+ size: sizeArg.optional(),
26095
+ 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."),
26096
+ 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
26097
  },
25608
26098
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
25609
26099
  },
@@ -25614,10 +26104,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25614
26104
  const me = await api.me();
25615
26105
  const data = { ...spec.data, ...parseData(args.data), [KIND_FIELD]: args.kind };
25616
26106
  if (args.label) data.label = args.label;
26107
+ let box = spec.size;
26108
+ if (typeof args.size === "string") data.size = textSizeFor(args.kind, args.size);
26109
+ else if (args.size) {
26110
+ const b = boxOf(args.size);
26111
+ box = { w: b.w ?? spec.size.w, h: b.h ?? spec.size.h };
26112
+ }
25617
26113
  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, spec.size, near, args.inputs?.[0] ? "right" : "left");
26114
+ const position = args.position ?? placeFor(doc, box, near, args.inputs?.[0] ? "right" : "left");
25619
26115
  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: spec.size, zIndex, createdBy: me });
26116
+ const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: box, zIndex, createdBy: me });
25621
26117
  const links = [
25622
26118
  ...(args.inputs ?? []).map((l) => ({ source: l.from, sourcePort: l.from_port, target: node.id, targetPort: l.to_port })),
25623
26119
  ...(args.outputs ?? []).map((l) => ({ source: node.id, sourcePort: l.from_port, target: l.to, targetPort: l.to_port }))
@@ -25631,10 +26127,12 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25631
26127
  }
25632
26128
  }
25633
26129
  const edges = edgeOps(doc, links, me, /* @__PURE__ */ new Set([node.id]));
26130
+ const drawn = edges.ops.flatMap((op) => op.type === "edge.add" ? [op.edge] : []);
26131
+ const mention = await mentionOps(doc, [{ node, data: node.data, keys: Object.keys(args.data ?? {}) }], drawn);
25634
26132
  return {
25635
- ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops],
26133
+ ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops, ...mention.ops],
25636
26134
  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}`
26135
+ 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
26136
  };
25639
26137
  })
25640
26138
  )
@@ -25643,48 +26141,90 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25643
26141
  "canvas_update_node",
25644
26142
  {
25645
26143
  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,
26144
+ 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
26145
  inputSchema: {
25648
26146
  board_id: boardId2,
25649
- node_id: nodeIdArg,
26147
+ node_id: nodeIdArg2,
25650
26148
  data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave."),
25651
26149
  label: external_exports.string().trim().min(1).max(120).optional(),
25652
- position: point.optional()
26150
+ position: point.optional(),
26151
+ size: sizeArg.optional()
25653
26152
  },
25654
26153
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25655
26154
  },
25656
26155
  async (args) => run(
25657
26156
  () => edit(args.board_id, async (doc) => {
25658
26157
  const node = needNode(doc, args.node_id);
25659
- const ops = [];
25660
- const patch = parseData(args.data);
25661
- for (const key of Object.keys(patch)) legacySequenceGuard(node, key);
25662
- if (args.label) patch.label = args.label;
25663
- for (const [k, v] of Object.entries(patch)) {
25664
- const path = `data.${k}`;
25665
- if (v === null) {
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"}` };
26158
+ const c = changeOps(node, args);
26159
+ const mention = await mentionOps(doc, [{ node, data: c.data, keys: c.dataKeys }]);
26160
+ return {
26161
+ ops: [...c.ops, ...mention.ops],
26162
+ targets: [node.id],
26163
+ done: `N\xF3 "${nodeName(node)}" atualizado: ${c.keys.join(", ") || "nada mudou"}${mention.note}`
26164
+ };
25677
26165
  })
25678
26166
  )
25679
26167
  );
26168
+ server.registerTool(
26169
+ "canvas_update_nodes",
26170
+ {
26171
+ title: "Canvas: editar v\xE1rios n\xF3s",
26172
+ 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.',
26173
+ inputSchema: {
26174
+ board_id: boardId2,
26175
+ nodes: external_exports.array(
26176
+ external_exports.object({
26177
+ node_id: nodeIdArg2,
26178
+ position: point.optional(),
26179
+ size: sizeArg.optional(),
26180
+ label: external_exports.string().trim().min(1).max(120).optional(),
26181
+ data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave.")
26182
+ }).strict()
26183
+ ).min(1).max(200),
26184
+ layout_only: external_exports.boolean().optional().describe("true: s\xF3 position e size, sem releitura nem recusa por conflito.")
26185
+ },
26186
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
26187
+ },
26188
+ async (args) => run(
26189
+ () => edit(
26190
+ args.board_id,
26191
+ async (doc) => {
26192
+ const ids = args.nodes.map((n) => n.node_id);
26193
+ const dup = ids.find((id, i) => ids.indexOf(id) !== i);
26194
+ if (dup) throw new CanvasToolError(`O n\xF3 ${dup} aparece duas vezes: junte as mudan\xE7as dele num item s\xF3.`);
26195
+ if (args.layout_only) {
26196
+ const bad = args.nodes.find((n) => n.data !== void 0 || n.label !== void 0 || typeof n.size === "string");
26197
+ if (bad) {
26198
+ throw new CanvasToolError(
26199
+ `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.`
26200
+ );
26201
+ }
26202
+ }
26203
+ const changes = args.nodes.map((n) => {
26204
+ const node = needNode(doc, n.node_id);
26205
+ return { node, ...changeOps(node, n) };
26206
+ });
26207
+ const mention = args.layout_only ? { ops: [], note: "" } : await mentionOps(doc, changes.map((c) => ({ node: c.node, data: c.data, keys: c.dataKeys })));
26208
+ const touched = changes.filter((c) => c.ops.length);
26209
+ const list = touched.slice(0, 12).map((c) => `"${oneLine(nodeName(c.node), 30)}" (${c.keys.join(", ")})`).join("; ");
26210
+ return {
26211
+ ops: [...changes.flatMap((c) => c.ops), ...mention.ops],
26212
+ targets: ids,
26213
+ 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
26214
+ };
26215
+ },
26216
+ { split: true, layoutOnly: !!args.layout_only }
26217
+ )
26218
+ )
26219
+ );
25680
26220
  server.registerTool(
25681
26221
  "canvas_patch_node",
25682
26222
  {
25683
26223
  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,
26224
+ 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
26225
  inputSchema: {
25686
26226
  board_id: boardId2,
25687
- node_id: nodeIdArg,
26227
+ node_id: nodeIdArg2,
25688
26228
  sets: external_exports.array(
25689
26229
  external_exports.object({
25690
26230
  path: external_exports.string().trim().min(1).max(500).describe('Caminho dentro do data (ex.: "timeline.meta.fps"); aceita o prefixo "data.".'),
@@ -25704,6 +26244,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25704
26244
  const ops = [];
25705
26245
  let same = 0;
25706
26246
  let absent = 0;
26247
+ const mentionKeys = /* @__PURE__ */ new Set();
26248
+ let mentionAt = -1;
25707
26249
  for (const [i, item] of args.sets.entries()) {
25708
26250
  const at = `sets[${i}] (${item.path})`;
25709
26251
  const segs = dataSegments(item.path);
@@ -25713,6 +26255,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25713
26255
  const path = `data.${segs.join(".")}`;
25714
26256
  const badPath = checkNodePath(path);
25715
26257
  if (badPath) throw new CanvasToolError(`${at}: ${badPath}.`);
26258
+ if (MENTION_KEYS.includes(segs[0])) mentionKeys.add(segs[0]);
25716
26259
  const unset = item.unset === true;
25717
26260
  const hasValue = item.value !== void 0;
25718
26261
  if (unset === hasValue) throw new CanvasToolError(`${at}: mande value OU unset: true.`);
@@ -25747,6 +26290,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25747
26290
  }
25748
26291
  delete cur[leaf];
25749
26292
  ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
26293
+ if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
25750
26294
  continue;
25751
26295
  }
25752
26296
  const value = item.value;
@@ -25756,12 +26300,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25756
26300
  }
25757
26301
  if (cur) cur[leaf] = value;
25758
26302
  ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(value) });
26303
+ if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
25759
26304
  }
25760
26305
  const skipped = [same ? `${same} igual(is) ao que j\xE1 estava` : "", absent ? `${absent} unset de caminho que n\xE3o existe` : ""].filter(Boolean).join(", ");
26306
+ const written = ops.length;
26307
+ const mention = await mentionOps(doc, [{ node, data: work, keys: [...mentionKeys] }]);
26308
+ ops.splice(mentionAt < 0 ? ops.length : mentionAt, 0, ...mention.ops);
25761
26309
  return {
25762
26310
  ops,
25763
26311
  targets: [node.id],
25764
- done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${ops.length} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "")
26312
+ done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${written} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "") + mention.note
25765
26313
  };
25766
26314
  },
25767
26315
  { split: true }
@@ -25775,9 +26323,9 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25775
26323
  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
26324
  inputSchema: {
25777
26325
  board_id: boardId2,
25778
- source: nodeIdArg,
26326
+ source: nodeIdArg2,
25779
26327
  source_port: external_exports.string().min(1).max(80),
25780
- target: nodeIdArg,
26328
+ target: nodeIdArg2,
25781
26329
  target_port: external_exports.string().min(1).max(80)
25782
26330
  },
25783
26331
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
@@ -25802,8 +26350,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25802
26350
  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
26351
  inputSchema: {
25804
26352
  board_id: boardId2,
25805
- node_ids: external_exports.array(nodeIdArg).max(200).optional(),
25806
- edge_ids: external_exports.array(nodeIdArg).max(200).optional()
26353
+ node_ids: external_exports.array(nodeIdArg2).max(200).optional(),
26354
+ edge_ids: external_exports.array(nodeIdArg2).max(200).optional()
25807
26355
  },
25808
26356
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25809
26357
  },
@@ -25904,13 +26452,14 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25904
26452
  );
25905
26453
  registerCanvasCommentTools(server, api, run);
25906
26454
  registerCanvasMediaTools(server, { api, cfg, run, edit, seen, placeFor });
26455
+ registerCanvasLayoutTools(server, { api, run, edit });
25907
26456
  }
25908
26457
 
25909
26458
  // src/version.ts
25910
26459
  import { createRequire } from "module";
25911
26460
  var require2 = createRequire(import.meta.url);
25912
26461
  function readVersion() {
25913
- if ("0.28.0") return "0.28.0";
26462
+ if ("0.29.0") return "0.29.0";
25914
26463
  try {
25915
26464
  const pkg = require2("../package.json");
25916
26465
  return pkg.version ?? "0.0.0-unknown";
@@ -25926,6 +26475,7 @@ var SERVER_INSTRUCTIONS = [
25926
26475
  '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
26476
  "Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
25928
26477
  "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.",
26478
+ '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
26479
  '"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
26480
  ].join("\n");
25931
26481
  function buildServer(cfg) {
@@ -26020,7 +26570,7 @@ function serversNaming(entry) {
26020
26570
  if (!servers || typeof servers !== "object") return [];
26021
26571
  return Object.entries(servers).filter(([name, value]) => name === "beplus" || JSON.stringify(value ?? null).includes("beplus-mcp")).map(([name]) => name);
26022
26572
  }
26023
- function checkConfigs(report) {
26573
+ function checkConfigs(report2) {
26024
26574
  let found = 0;
26025
26575
  for (const { path, client } of configCandidates()) {
26026
26576
  let raw;
@@ -26033,7 +26583,7 @@ function checkConfigs(report) {
26033
26583
  try {
26034
26584
  parsed = JSON.parse(raw);
26035
26585
  } catch {
26036
- report.line("aviso", `Config ilegivel: ${path}`, "O arquivo existe mas nao e JSON valido.");
26586
+ report2.line("aviso", `Config ilegivel: ${path}`, "O arquivo existe mas nao e JSON valido.");
26037
26587
  continue;
26038
26588
  }
26039
26589
  const names = new Set(serversNaming(parsed));
@@ -26043,25 +26593,25 @@ function checkConfigs(report) {
26043
26593
  }
26044
26594
  if (names.size === 0) continue;
26045
26595
  found += 1;
26046
- report.line("ok", `Config detectado: ${path}`, `${client}, servidor ${[...names].join(", ")}`);
26596
+ report2.line("ok", `Config detectado: ${path}`, `${client}, servidor ${[...names].join(", ")}`);
26047
26597
  }
26048
26598
  if (found === 0) {
26049
- report.line(
26599
+ report2.line(
26050
26600
  "aviso",
26051
26601
  "Nenhum config de cliente MCP com o beplus foi encontrado.",
26052
26602
  "Isso e normal se voce ainda nao instalou o servidor, ou se o cliente guarda o config em outro lugar."
26053
26603
  );
26054
26604
  }
26055
26605
  }
26056
- async function checkApiReachable(report, baseUrl) {
26606
+ async function checkApiReachable(report2, baseUrl) {
26057
26607
  const controller = new AbortController();
26058
26608
  const timer = setTimeout(() => controller.abort(), 1e4);
26059
26609
  try {
26060
26610
  const res = await fetch(`${baseUrl}/api/v1/mcp/me`, { signal: controller.signal });
26061
- report.line("ok", `API responde: ${baseUrl}`, `GET /api/v1/mcp/me sem token devolveu HTTP ${res.status}.`);
26611
+ report2.line("ok", `API responde: ${baseUrl}`, `GET /api/v1/mcp/me sem token devolveu HTTP ${res.status}.`);
26062
26612
  } catch (e) {
26063
26613
  const aborted2 = e?.name === "AbortError";
26064
- report.line(
26614
+ report2.line(
26065
26615
  "erro",
26066
26616
  `API nao respondeu: ${baseUrl}`,
26067
26617
  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 +26620,10 @@ async function checkApiReachable(report, baseUrl) {
26070
26620
  clearTimeout(timer);
26071
26621
  }
26072
26622
  }
26073
- async function checkToken(report) {
26623
+ async function checkToken(report2) {
26074
26624
  const token = process.env.BEPLUS_API_TOKEN?.trim();
26075
26625
  if (!token) {
26076
- report.line(
26626
+ report2.line(
26077
26627
  "aviso",
26078
26628
  "BEPLUS_API_TOKEN nao esta definido.",
26079
26629
  "Gere um Personal Access Token em Tokens de acesso, no painel BePlus, e coloque no config do MCP."
@@ -26082,46 +26632,46 @@ async function checkToken(report) {
26082
26632
  }
26083
26633
  try {
26084
26634
  const me = await new BeplusClient(loadConfig()).me();
26085
- report.line(
26635
+ report2.line(
26086
26636
  "ok",
26087
26637
  `Token valido (${maskToken(token)})`,
26088
26638
  `Conta: ${me.name ?? me.email ?? me.userId}, ${me.diamonds} diamantes.`
26089
26639
  );
26090
26640
  } catch (e) {
26091
26641
  if (e instanceof ApiError && e.httpStatus === 401) {
26092
- report.line(
26642
+ report2.line(
26093
26643
  "erro",
26094
26644
  `Token invalido ou revogado (${maskToken(token)})`,
26095
26645
  "Gere um novo Personal Access Token no painel BePlus e atualize BEPLUS_API_TOKEN no config do MCP."
26096
26646
  );
26097
26647
  return;
26098
26648
  }
26099
- report.line("aviso", "Nao deu para validar o token agora.", e?.message ?? "erro desconhecido");
26649
+ report2.line("aviso", "Nao deu para validar o token agora.", e?.message ?? "erro desconhecido");
26100
26650
  }
26101
26651
  }
26102
26652
  async function runDoctor() {
26103
- const report = new Report();
26653
+ const report2 = new Report();
26104
26654
  process.stdout.write(`beplus-mcp doctor, versao ${VERSION}
26105
26655
 
26106
26656
  `);
26107
26657
  if (isSupportedNode()) {
26108
- report.line("ok", `Node ${process.versions.node}`, `Minimo exigido: Node ${MIN_NODE_MAJOR}.`);
26658
+ report2.line("ok", `Node ${process.versions.node}`, `Minimo exigido: Node ${MIN_NODE_MAJOR}.`);
26109
26659
  } else {
26110
- report.line("erro", `Node ${process.versions.node}`, unsupportedNodeMessage());
26660
+ report2.line("erro", `Node ${process.versions.node}`, unsupportedNodeMessage());
26111
26661
  }
26112
26662
  let baseUrl = null;
26113
26663
  try {
26114
26664
  baseUrl = resolveBaseUrl();
26115
- report.line("ok", `BEPLUS_API_URL: ${baseUrl}`);
26665
+ report2.line("ok", `BEPLUS_API_URL: ${baseUrl}`);
26116
26666
  } catch (e) {
26117
- report.line("erro", "BEPLUS_API_URL invalida", e.message);
26667
+ report2.line("erro", "BEPLUS_API_URL invalida", e.message);
26118
26668
  }
26119
26669
  if (baseUrl) {
26120
- await checkApiReachable(report, baseUrl);
26121
- await checkToken(report);
26670
+ await checkApiReachable(report2, baseUrl);
26671
+ await checkToken(report2);
26122
26672
  }
26123
- checkConfigs(report);
26124
- const code = report.exitCode;
26673
+ checkConfigs(report2);
26674
+ const code = report2.exitCode;
26125
26675
  process.stdout.write(code === 0 ? "\nTudo certo.\n" : "\nTem item com [erro] acima. Resolva antes de usar o MCP.\n");
26126
26676
  return code;
26127
26677
  }