beplus-mcp 0.36.0 → 0.37.1

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
@@ -24437,7 +24437,7 @@ var KINDS = {
24437
24437
  block: { title: "Bloco", size: { w: 260, h: 140 }, data: { text: "" }, inputs: [], outputs: ["text"], main: "text" },
24438
24438
  prompt: {
24439
24439
  title: "Prompt",
24440
- size: { w: 340, h: 200 },
24440
+ size: { w: 1024, h: 516 },
24441
24441
  data: { template: "" },
24442
24442
  inputs: ["refs"],
24443
24443
  outputs: ["text", "refs"],
@@ -24488,7 +24488,7 @@ var KINDS = {
24488
24488
  media: { title: "M\xEDdia", size: { w: 320, h: 280 }, data: { items: [] }, inputs: [], outputs: ["image"], dynamic: "sa\xEDda por tipo de item (image, video, audio, file)", main: "items" },
24489
24489
  image: {
24490
24490
  title: "Gerador de imagem",
24491
- size: { w: 320, h: 380 },
24491
+ size: { w: 516, h: 516 },
24492
24492
  data: { model: "google/nano-banana-2", aspect_ratio: "1:1", count: 1 },
24493
24493
  inputs: ["prompt"],
24494
24494
  outputs: ["image"],
@@ -24496,7 +24496,7 @@ var KINDS = {
24496
24496
  },
24497
24497
  video: {
24498
24498
  title: "Gerador de v\xEDdeo",
24499
- size: { w: 360, h: 320 },
24499
+ size: { w: 917, h: 516 },
24500
24500
  data: { model: "bytedance/seedance-2.0-mini", aspect_ratio: "16:9", duration: 5, generateAudio: true },
24501
24501
  inputs: ["prompt"],
24502
24502
  outputs: ["video"],
@@ -24585,6 +24585,28 @@ var KINDS = {
24585
24585
  main: "text"
24586
24586
  }
24587
24587
  };
24588
+ var LABEL_BIRTH_WIDTH = { titulo: 2e3, subtitulo: 1200, corpo: 320 };
24589
+ function labelBirthWidth(size) {
24590
+ return typeof size === "string" && Object.hasOwn(LABEL_BIRTH_WIDTH, size) ? LABEL_BIRTH_WIDTH[size] : LABEL_BIRTH_WIDTH.titulo;
24591
+ }
24592
+ var CREATION_HEIGHT = 516;
24593
+ var STAGE_RATIO_MIN = 0.5;
24594
+ var STAGE_RATIO_MAX = 2.4;
24595
+ function ratioOfAspect(aspect) {
24596
+ if (typeof aspect !== "string") return null;
24597
+ const match = /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/.exec(aspect.trim());
24598
+ if (match === null) return null;
24599
+ const width2 = Number(match[1]);
24600
+ const height = Number(match[2]);
24601
+ return width2 > 0 && height > 0 ? width2 / height : null;
24602
+ }
24603
+ function isGeneratorKind(kind) {
24604
+ return kind === "image" || kind === "video";
24605
+ }
24606
+ function generatorBirthWidth(kind, aspect) {
24607
+ const ratio = ratioOfAspect(aspect) ?? (kind === "video" ? 16 / 9 : 1);
24608
+ return Math.round(CREATION_HEIGHT * Math.min(STAGE_RATIO_MAX, Math.max(STAGE_RATIO_MIN, ratio)));
24609
+ }
24588
24610
  function kindList() {
24589
24611
  return Object.entries(KINDS).map(([k, s]) => `${k} (${s.title})`).join(", ");
24590
24612
  }
@@ -24826,7 +24848,7 @@ function sizeLabel(node) {
24826
24848
  if (u.source === "medido" && u.estimated) return `${base} (medido em ${Math.round(readMeasured(node).w)} de largura: altura estimada)`;
24827
24849
  return base;
24828
24850
  }
24829
- var DEFAULT_WIDTHS = { prompt: 1024, image: 560, video: 560, reference: 320, frame: 320, title: 1024 };
24851
+ var DEFAULT_WIDTHS = { prompt: 1024, reference: 320, frame: 320, title: 1024 };
24830
24852
  var DEFAULT_GAP = 80;
24831
24853
  var FRAME_PORTS = /* @__PURE__ */ new Set(["frame"]);
24832
24854
  var FEEDS = /* @__PURE__ */ new Set(["prompt", "image", "video"]);
@@ -24848,9 +24870,14 @@ function hangsRight(doc, node) {
24848
24870
  return out.some((e) => doc.nodes[e.target] && FEEDS.has(kindOf(doc.nodes[e.target])));
24849
24871
  }
24850
24872
  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;
24851
- function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDTHS) {
24873
+ function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = {}, scope) {
24852
24874
  const placed = [];
24853
- const floorOf = (node, role) => role === "other" ? node.size.w : widths[role];
24875
+ const floorOf = (node, role) => {
24876
+ if (role === "other") return node.size.w;
24877
+ const given = widths[role] ?? DEFAULT_WIDTHS[role];
24878
+ if (given !== void 0) return given;
24879
+ return generatorBirthWidth(role === "video" ? "video" : "image", node.data.aspect_ratio);
24880
+ };
24854
24881
  const place = (id, row, asTitle, x, y2, alignRight) => {
24855
24882
  const node = doc.nodes[id];
24856
24883
  const role = roleOf(doc, node, asTitle);
@@ -24890,7 +24917,7 @@ function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDT
24890
24917
  for (let j = i + 1; j < placed.length; j++) if (overlap(placed[i], placed[j])) inner.push([placed[i].id, placed[j].id]);
24891
24918
  }
24892
24919
  const ids = new Set(placed.map((p) => p.id));
24893
- 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) }));
24920
+ const others = Object.values(doc.nodes).filter((n) => !ids.has(n.id) && kindOf(n) !== "section" && (scope === void 0 || scope.has(n.id))).map((n) => ({ id: n.id, x: n.position.x, y: n.position.y, ...usedSize(n) }));
24894
24921
  const outer = [];
24895
24922
  for (const p of placed) for (const o of others) if (overlap(p, o)) outer.push([p.id, o.id]);
24896
24923
  const left = Math.min(origin.x, ...placed.map((p) => p.x));
@@ -24900,6 +24927,322 @@ function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDT
24900
24927
  return { placed, inner, outer, freeOrigin };
24901
24928
  }
24902
24929
 
24930
+ // src/canvas/tabs.ts
24931
+ var TAB_FIELD = "aba";
24932
+ var TAB_ID_FIELD = "abaId";
24933
+ var TAB_ORDER_FIELD = "abaOrdem";
24934
+ var TAB_NEAR = 2e3;
24935
+ var AVULSOS_TAB = "avulsos";
24936
+ var SECTION = "section";
24937
+ var isFiniteNumber2 = (v) => typeof v === "number" && Number.isFinite(v);
24938
+ function isArchived(node) {
24939
+ const flag = node?.data["archived"];
24940
+ return typeof flag === "object" && flag !== null;
24941
+ }
24942
+ function isSectionNode(node) {
24943
+ return node !== void 0 && node.data[KIND_FIELD] === SECTION;
24944
+ }
24945
+ function isTabSection(node) {
24946
+ return node !== void 0 && isSectionNode(node) && node.data[TAB_FIELD] === true && !isArchived(node);
24947
+ }
24948
+ var rectHas = (r, x, y) => x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
24949
+ function distanceTo(r, x, y) {
24950
+ const dx = Math.max(r.left - x, 0, x - r.right);
24951
+ const dy = Math.max(r.top - y, 0, y - r.bottom);
24952
+ return Math.hypot(dx, dy);
24953
+ }
24954
+ function boardPosition(doc, node) {
24955
+ let x = node.position.x;
24956
+ let y = node.position.y;
24957
+ const seen = /* @__PURE__ */ new Set([node.id]);
24958
+ let parent = node.parentId === null ? void 0 : doc.nodes[node.parentId];
24959
+ while (parent !== void 0 && !seen.has(parent.id)) {
24960
+ seen.add(parent.id);
24961
+ x += parent.position.x;
24962
+ y += parent.position.y;
24963
+ parent = parent.parentId === null ? void 0 : doc.nodes[parent.parentId];
24964
+ }
24965
+ return { x, y };
24966
+ }
24967
+ function sectionRect(doc, node) {
24968
+ const at = boardPosition(doc, node);
24969
+ return { left: at.x, top: at.y, right: at.x + node.size.w, bottom: at.y + node.size.h };
24970
+ }
24971
+ function savedMeasure(node) {
24972
+ const value = node.data[MEASURED_KEY];
24973
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
24974
+ const record2 = value;
24975
+ if (!isFiniteNumber2(record2["w"]) || !isFiniteNumber2(record2["h"])) return void 0;
24976
+ return { w: record2["w"], h: record2["h"] };
24977
+ }
24978
+ function drawnRect(doc, node) {
24979
+ const at = boardPosition(doc, node);
24980
+ const saved = savedMeasure(node);
24981
+ const w = Math.max(node.size.w, saved?.w ?? 0);
24982
+ const h = Math.max(node.size.h, saved?.h ?? 0);
24983
+ return { left: at.x, top: at.y, right: at.x + w, bottom: at.y + h };
24984
+ }
24985
+ function savedCenter(doc, node) {
24986
+ const at = boardPosition(doc, node);
24987
+ const saved = savedMeasure(node);
24988
+ const w = Math.max(node.size.w, saved?.w ?? 0);
24989
+ const h = Math.max(node.size.h, saved?.h ?? 0);
24990
+ return { x: at.x + w / 2, y: at.y + h / 2 };
24991
+ }
24992
+ function tabOrderOf(node) {
24993
+ const raw = node.data[TAB_ORDER_FIELD];
24994
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
24995
+ }
24996
+ function orderedTabSections(doc) {
24997
+ return Object.values(doc.nodes).filter((node) => isTabSection(node)).sort((a, b) => {
24998
+ const oa = tabOrderOf(a);
24999
+ const ob = tabOrderOf(b);
25000
+ if (oa !== null && ob === null) return -1;
25001
+ if (oa === null && ob !== null) return 1;
25002
+ if (oa !== null && ob !== null && oa !== ob) return oa - ob;
25003
+ return a.position.y - b.position.y || a.position.x - b.position.x;
25004
+ });
25005
+ }
25006
+ var partitions = /* @__PURE__ */ new WeakMap();
25007
+ function build(doc) {
25008
+ const tabs = orderedTabSections(doc).map((node) => ({
25009
+ id: node.id,
25010
+ ...sectionRect(doc, node)
25011
+ }));
25012
+ const live = new Map(tabs.map((tab) => [tab.id, tab]));
25013
+ const home = /* @__PURE__ */ new Map();
25014
+ const walking = /* @__PURE__ */ new Set();
25015
+ const byPlace = (node) => {
25016
+ const center = savedCenter(doc, node);
25017
+ for (const tab of tabs) if (rectHas(tab, center.x, center.y)) return tab.id;
25018
+ let near = null;
25019
+ let best = TAB_NEAR;
25020
+ for (const tab of tabs) {
25021
+ const distance = distanceTo(tab, center.x, center.y);
25022
+ if (distance <= best && (near === null || distance < best)) {
25023
+ near = tab;
25024
+ best = distance;
25025
+ }
25026
+ }
25027
+ return near === null ? AVULSOS_TAB : near.id;
25028
+ };
25029
+ const resolve3 = (node) => {
25030
+ const known = home.get(node.id);
25031
+ if (known !== void 0) return known;
25032
+ let tab;
25033
+ const mark = node.data[TAB_ID_FIELD];
25034
+ if (live.has(node.id)) {
25035
+ tab = node.id;
25036
+ } else if (typeof mark === "string" && mark !== "") {
25037
+ tab = live.has(mark) ? mark : AVULSOS_TAB;
25038
+ } else if (node.parentId !== null && !walking.has(node.id)) {
25039
+ const parent = doc.nodes[node.parentId];
25040
+ if (parent === void 0) {
25041
+ tab = byPlace(node);
25042
+ } else {
25043
+ walking.add(node.id);
25044
+ tab = resolve3(parent);
25045
+ walking.delete(node.id);
25046
+ }
25047
+ } else {
25048
+ tab = byPlace(node);
25049
+ }
25050
+ home.set(node.id, tab);
25051
+ return tab;
25052
+ };
25053
+ const members = /* @__PURE__ */ new Map();
25054
+ for (const node of Object.values(doc.nodes)) {
25055
+ if (isArchived(node)) continue;
25056
+ const tab = resolve3(node);
25057
+ const list = members.get(tab);
25058
+ if (list === void 0) members.set(tab, [node.id]);
25059
+ else list.push(node.id);
25060
+ }
25061
+ return { tabs, home, members };
25062
+ }
25063
+ function partitionOf(doc) {
25064
+ let part = partitions.get(doc);
25065
+ if (part === void 0) {
25066
+ part = build(doc);
25067
+ partitions.set(doc, part);
25068
+ }
25069
+ return part;
25070
+ }
25071
+
25072
+ // src/canvas/tab-scope.ts
25073
+ var LOCK_FIELD = "travada";
25074
+ var TAB_END_GAP = 120;
25075
+ var TAB_GROW_PAD = 48;
25076
+ var TAB_TOP_ROOM = 96;
25077
+ var TAB_CLEAR_GAP = 48;
25078
+ var SHORT_ID = 8;
25079
+ var MIN_PREFIX = 4;
25080
+ var LISTED = 12;
25081
+ var oneLine = (text, max) => {
25082
+ const flat = text.replace(/\s+/g, " ").trim();
25083
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
25084
+ };
25085
+ function tabTitle(node) {
25086
+ const title = typeof node.data.title === "string" ? node.data.title.trim() : "";
25087
+ const label = typeof node.data.label === "string" ? node.data.label.trim() : "";
25088
+ if (title !== "" && title !== "Se\xE7\xE3o") return title;
25089
+ return label || title || "Se\xE7\xE3o";
25090
+ }
25091
+ var isLockedTab = (node) => node !== void 0 && node.data[LOCK_FIELD] === true;
25092
+ function shortTabIds(ids) {
25093
+ const longest = Math.max(0, ...ids.map((id) => id.length));
25094
+ let length = SHORT_ID;
25095
+ while (length < longest && new Set(ids.map((id) => id.slice(0, length))).size < ids.length) length += 1;
25096
+ return new Map(ids.map((id) => [id, id.slice(0, length)]));
25097
+ }
25098
+ function tabIndex(doc) {
25099
+ const part = partitionOf(doc);
25100
+ const shorts = shortTabIds(part.tabs.map((tab) => tab.id));
25101
+ const tabs = part.tabs.map((tab) => {
25102
+ const node = doc.nodes[tab.id];
25103
+ return {
25104
+ id: tab.id,
25105
+ short: shorts.get(tab.id),
25106
+ title: tabTitle(node),
25107
+ locked: isLockedTab(node),
25108
+ cards: Math.max(0, (part.members.get(tab.id)?.length ?? 0) - 1)
25109
+ };
25110
+ });
25111
+ const byId = new Map(tabs.map((tab) => [tab.id, tab]));
25112
+ return {
25113
+ tabs,
25114
+ loose: part.members.get(AVULSOS_TAB)?.length ?? 0,
25115
+ label: (tab) => tab === AVULSOS_TAB ? AVULSOS_TAB : byId.get(tab)?.short ?? tab,
25116
+ info: (tab) => byId.get(tab)
25117
+ };
25118
+ }
25119
+ var hasTabs = (doc) => partitionOf(doc).tabs.length > 0;
25120
+ function homeTabOf(doc, nodeId3) {
25121
+ const part = partitionOf(doc);
25122
+ return part.tabs.length === 0 ? null : part.home.get(nodeId3) ?? null;
25123
+ }
25124
+ function membersOfTab(doc, tab) {
25125
+ return new Set(partitionOf(doc).members.get(tab) ?? []);
25126
+ }
25127
+ function tabList(doc) {
25128
+ const tabs = partitionOf(doc).tabs;
25129
+ if (tabs.length === 0) return "Este board n\xE3o tem abas (uma aba \xE9 uma se\xE7\xE3o com aba=true).";
25130
+ const shown = tabs.slice(0, LISTED).map((tab) => `"${oneLine(tabTitle(doc.nodes[tab.id]), 40)}" (${tab.id})`);
25131
+ const more = tabs.length > LISTED ? `, \u2026 e mais ${tabs.length - LISTED}` : "";
25132
+ return `Abas do board: ${shown.join(", ")}${more}.`;
25133
+ }
25134
+ function kindLabel(node) {
25135
+ const kind = node.data.kind;
25136
+ return typeof kind === "string" && kind ? kind : node.type;
25137
+ }
25138
+ function resolveTab(doc, ref, opts = {}) {
25139
+ const value = ref.trim();
25140
+ if (value === AVULSOS_TAB) {
25141
+ if (opts.allowAvulsos && hasTabs(doc)) return AVULSOS_TAB;
25142
+ throw new CanvasToolError(
25143
+ hasTabs(doc) ? '"avulsos" n\xE3o \xE9 uma aba: \xE9 onde ficam os cart\xF5es que n\xE3o moram em aba. Para criar sem aba, omita aba.' : `Aba "avulsos" n\xE3o existe. ${tabList(doc)}`
25144
+ );
25145
+ }
25146
+ if (Object.hasOwn(doc.nodes, value)) {
25147
+ const node = doc.nodes[value];
25148
+ if (isTabSection(node)) return node.id;
25149
+ const name = `"${oneLine(tabTitle(node), 40)}"`;
25150
+ if (isSectionNode(node) && node.data[TAB_FIELD] === true && isArchived(node)) {
25151
+ throw new CanvasToolError(
25152
+ `A aba ${name} (${value}) est\xE1 arquivada: os cart\xF5es que apontam para ela ficam em Avulsos. Escolha outra aba. ${tabList(doc)}`
25153
+ );
25154
+ }
25155
+ throw new CanvasToolError(
25156
+ `${value} \xE9 ${isSectionNode(node) ? "uma se\xE7\xE3o comum" : `um n\xF3 ${kindLabel(node)}`} (${name}), n\xE3o uma aba. Aba \xE9 a se\xE7\xE3o com aba=true. ${tabList(doc)}`
25157
+ );
25158
+ }
25159
+ if (value.length >= MIN_PREFIX) {
25160
+ const hits = partitionOf(doc).tabs.filter((tab) => tab.id.startsWith(value));
25161
+ if (hits.length === 1) return hits[0].id;
25162
+ if (hits.length > 1) {
25163
+ const names = hits.slice(0, LISTED).map((tab) => `"${oneLine(tabTitle(doc.nodes[tab.id]), 40)}" (${tab.id})`);
25164
+ throw new CanvasToolError(`"${value}" serve a mais de uma aba: ${names.join(", ")}. Use o id inteiro.`);
25165
+ }
25166
+ }
25167
+ throw new CanvasToolError(`Aba "${oneLine(value, 60)}" n\xE3o existe neste board. ${tabList(doc)}`);
25168
+ }
25169
+ function unionOf(rects) {
25170
+ let out = null;
25171
+ for (const r of rects) {
25172
+ out = out === null ? { ...r } : {
25173
+ left: Math.min(out.left, r.left),
25174
+ top: Math.min(out.top, r.top),
25175
+ right: Math.max(out.right, r.right),
25176
+ bottom: Math.max(out.bottom, r.bottom)
25177
+ };
25178
+ }
25179
+ return out;
25180
+ }
25181
+ function endOfTabContent(doc, tabId, size) {
25182
+ const part = partitionOf(doc);
25183
+ const section = doc.nodes[tabId];
25184
+ const boxes = (part.members.get(tabId) ?? []).flatMap((id) => {
25185
+ const node = doc.nodes[id];
25186
+ return id === tabId || node === void 0 || isArchived(node) ? [] : [drawnRect(doc, node)];
25187
+ });
25188
+ const content = unionOf(boxes);
25189
+ const frame = sectionRect(doc, section);
25190
+ const spot = content === null ? { x: frame.left + TAB_GROW_PAD, y: frame.top + TAB_TOP_ROOM } : { x: content.left, y: content.bottom + TAB_END_GAP };
25191
+ const others = part.tabs.filter((tab) => tab.id !== tabId);
25192
+ const cx = spot.x + size.w / 2;
25193
+ let y = spot.y;
25194
+ for (let guard = 0; guard <= others.length; guard += 1) {
25195
+ const hit = others.find((rect) => rectHas(rect, cx, y + size.h / 2));
25196
+ if (hit === void 0) break;
25197
+ y = hit.bottom + TAB_CLEAR_GAP;
25198
+ }
25199
+ return { x: Math.round(spot.x), y: Math.round(y) };
25200
+ }
25201
+ function placementNote(doc, node) {
25202
+ if (isTabSection(node)) return "";
25203
+ const after = { ...doc, nodes: { ...doc.nodes, [node.id]: node } };
25204
+ const index = tabIndex(after);
25205
+ if (index.tabs.length === 0) return "";
25206
+ const home = partitionOf(after).home.get(node.id);
25207
+ if (home === void 0) return "";
25208
+ const info = index.info(home);
25209
+ if (info === void 0) {
25210
+ return " Aten\xE7\xE3o: ficou em Avulsos (nenhuma aba o segura); passe aba=<id da aba> para ele morar numa aba.";
25211
+ }
25212
+ return ` Aba: "${oneLine(info.title, 40)}" (aba=${info.short}).`;
25213
+ }
25214
+ function flatten(ops) {
25215
+ return ops.flatMap((op) => op.type === "batch" ? flatten(op.ops) : [op]);
25216
+ }
25217
+ function badAbaId(doc, value) {
25218
+ if (value === null) return null;
25219
+ if (typeof value !== "string") return `data.${TAB_ID_FIELD} precisa ser o id (texto) de uma aba, ou null para tirar o campo.`;
25220
+ if (Object.hasOwn(doc.nodes, value) && isTabSection(doc.nodes[value])) return null;
25221
+ let hint;
25222
+ try {
25223
+ const full = resolveTab(doc, value);
25224
+ hint = `Use o id inteiro: ${full}.`;
25225
+ } catch (e) {
25226
+ hint = e instanceof Error ? e.message : "";
25227
+ }
25228
+ return `data.${TAB_ID_FIELD} "${oneLine(value, 60)}" n\xE3o \xE9 o id de uma aba viva. ${hint} Para tirar o campo, mande null.`;
25229
+ }
25230
+ function abaIdViolation(doc, ops) {
25231
+ for (const op of flatten(ops)) {
25232
+ if (op.type === "node.add") {
25233
+ const value = op.node.data[TAB_ID_FIELD];
25234
+ if (value !== void 0) {
25235
+ const bad = badAbaId(doc, value);
25236
+ if (bad) return bad;
25237
+ }
25238
+ } else if (op.type === "node.set" && op.path === `data.${TAB_ID_FIELD}` && op.value.kind === "value") {
25239
+ const bad = badAbaId(doc, op.value.value);
25240
+ if (bad) return bad;
25241
+ }
25242
+ }
25243
+ return null;
25244
+ }
25245
+
24903
25246
  // src/canvas/summary.ts
24904
25247
  var MAX_TEXT = 160;
24905
25248
  var MAX_BLOCKS = 40;
@@ -24931,7 +25274,7 @@ function visibleData(data) {
24931
25274
  return rest;
24932
25275
  }
24933
25276
  var NAME_KEYS = /* @__PURE__ */ new Set([KIND_FIELD, "label", "nodeTitle"]);
24934
- function oneLine(text, max = MAX_TEXT) {
25277
+ function oneLine2(text, max = MAX_TEXT) {
24935
25278
  const flat = text.replace(/\s+/g, " ").trim();
24936
25279
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
24937
25280
  }
@@ -24950,7 +25293,7 @@ function scalar(v) {
24950
25293
  if (typeof v === "string") {
24951
25294
  if (v.startsWith("data:")) return "[data-url]";
24952
25295
  if (/^https?:\/\//.test(v)) return "[url]";
24953
- return JSON.stringify(oneLine(v, 60));
25296
+ return JSON.stringify(oneLine2(v, 60));
24954
25297
  }
24955
25298
  if (typeof v === "number" || typeof v === "boolean" || v === null) return String(v);
24956
25299
  return null;
@@ -24959,7 +25302,7 @@ function blocksLines(blocks) {
24959
25302
  if (!Array.isArray(blocks)) return [];
24960
25303
  const out = blocks.slice(0, MAX_BLOCKS).map((b) => {
24961
25304
  const rec = b ?? {};
24962
- return ` \xB7 ${String(rec.id ?? "?")}: ${oneLine(String(rec.text ?? ""), 120)}`;
25305
+ return ` \xB7 ${String(rec.id ?? "?")}: ${oneLine2(String(rec.text ?? ""), 120)}`;
24963
25306
  });
24964
25307
  if (blocks.length > MAX_BLOCKS) out.push(` \xB7 \u2026 +${blocks.length - MAX_BLOCKS} blocos`);
24965
25308
  return out;
@@ -24972,21 +25315,22 @@ function rowsLines(rows) {
24972
25315
  const ideas = Array.isArray(rec.ideas) ? rec.ideas : [];
24973
25316
  const ideaText = ideas.slice(0, 6).map((i) => {
24974
25317
  const idea = i ?? {};
24975
- return `${String(idea.id ?? "?")}="${oneLine(String(idea.text ?? ""), 60)}"`;
25318
+ return `${String(idea.id ?? "?")}="${oneLine2(String(idea.text ?? ""), 60)}"`;
24976
25319
  }).join("; ");
24977
25320
  out.push(` \xB7 linha ${String(rec.id ?? "?")} (bloco ${String(rec.narrationBlockId ?? "?")}): ${ideas.length} ideia(s) ${ideaText}`);
24978
25321
  }
24979
25322
  if (rows.length > MAX_BLOCKS) out.push(` \xB7 \u2026 +${rows.length - MAX_BLOCKS} linhas`);
24980
25323
  return out;
24981
25324
  }
24982
- function nodeLines(node, run) {
25325
+ function nodeLines(node, run, aba) {
24983
25326
  const kind = nodeKind(node);
24984
25327
  const spec = kind ? KINDS[kind] : void 0;
24985
- 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)}`;
25328
+ const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine2(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)}) ${sizeLabel(node)}` + (aba ? ` \xB7 aba=${aba}` : "");
24986
25329
  const facts = [];
24987
25330
  const extra = [];
24988
25331
  const d = node.data;
24989
25332
  if (d.archived) facts.push("ARQUIVADO");
25333
+ if (aba && isTabSection(node)) facts.push("\xE9 aba");
24990
25334
  const approval = d.approval?.state;
24991
25335
  if (typeof approval === "string") facts.push(`aprova\xE7\xE3o: ${approval}`);
24992
25336
  if (Array.isArray(d.history)) facts.push(`${d.history.length} gera\xE7\xE3o(\xF5es)`);
@@ -25007,10 +25351,11 @@ function nodeLines(node, run) {
25007
25351
  if (fact) facts.push(fact);
25008
25352
  }
25009
25353
  const main2 = spec?.main;
25010
- if (main2 && typeof d[main2] === "string" && d[main2]) facts.push(`${main2}: "${oneLine(d[main2])}"`);
25354
+ if (main2 && typeof d[main2] === "string" && d[main2]) facts.push(`${main2}: "${oneLine2(d[main2])}"`);
25011
25355
  for (const [key, value] of Object.entries(d)) {
25012
25356
  if (NAME_KEYS.has(key) || HEAVY_KEYS.has(key) || key === main2) continue;
25013
25357
  if (["blocks", "rows", "items", "archived", "approval"].includes(key)) continue;
25358
+ if (aba && (key === TAB_ID_FIELD || key === TAB_FIELD && isTabSection(node))) continue;
25014
25359
  const s = scalar(value);
25015
25360
  if (s !== null) facts.push(`${key}=${s}`);
25016
25361
  else if (Array.isArray(value)) facts.push(`${key}=[${value.length}]`);
@@ -25032,27 +25377,63 @@ function timelineFact(d) {
25032
25377
  const clips = seq.audioClips && typeof seq.audioClips === "object" && !Array.isArray(seq.audioClips) ? Object.values(seq.audioClips).reduce((t, list) => t + (Array.isArray(list) ? list.length : 0), 0) : 0;
25033
25378
  return `sequ\xEAncia (legado, sem data.timeline): ${plans} plano(s), ${clips} clipe(s) de \xE1udio, ${JSON.stringify(seq).length} caracteres (inteira em canvas_read_node sem path; ${volume})`;
25034
25379
  }
25035
- function boardSummary(snap) {
25380
+ var cardsText = (n) => n === 1 ? "1 cart\xE3o" : `${n} cart\xF5es`;
25381
+ function boardSummary(snap, opts = {}) {
25036
25382
  const doc = snap.doc;
25037
- const nodes = Object.values(doc.nodes).sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
25038
- const edges = Object.values(doc.edges);
25383
+ const part = partitionOf(doc);
25384
+ const index = tabIndex(doc);
25385
+ const tabbed = index.tabs.length > 0;
25386
+ const scope = opts.tab === void 0 ? null : new Set(part.members.get(opts.tab) ?? []);
25387
+ const everyNode = Object.values(doc.nodes).sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
25388
+ const everyEdge = Object.values(doc.edges);
25389
+ const nodes = scope ? everyNode.filter((n) => scope.has(n.id)) : everyNode;
25390
+ const edges = scope ? everyEdge.filter((e) => scope.has(e.source) || scope.has(e.target)) : everyEdge;
25039
25391
  const runs = new Map(snap.runs.map((r) => [r.nodeId, r]));
25040
- const lines = [
25041
- `Board "${snap.title}" \xB7 id ${snap.id} \xB7 seq ${snap.seq} \xB7 seu papel: ${snap.role} \xB7 ${nodes.length} n\xF3(s) \xB7 ${edges.length} liga\xE7\xE3o(\xF5es)`
25042
- ];
25392
+ const who2 = `Board "${snap.title}" \xB7 id ${snap.id} \xB7 seq ${snap.seq} \xB7 seu papel: ${snap.role}`;
25393
+ const lines = [];
25394
+ if (scope === null) {
25395
+ lines.push(`${who2} \xB7 ${nodes.length} n\xF3(s) \xB7 ${edges.length} liga\xE7\xE3o(\xF5es)`);
25396
+ } else {
25397
+ const external = edges.filter((e) => !(scope.has(e.source) && scope.has(e.target))).length;
25398
+ const info = index.info(opts.tab);
25399
+ const name = info ? `a aba "${oneLine2(info.title, 60)}" (aba=${info.short})` : "os avulsos (aba=avulsos)";
25400
+ lines.push(
25401
+ `${who2} \xB7 s\xF3 ${name}: ${nodes.length} n\xF3(s) \xB7 ${edges.length} liga\xE7\xE3o(\xF5es)${external ? `, ${external} externa(s)` : ""}`,
25402
+ `O board inteiro tem ${everyNode.length} n\xF3(s) e ${everyEdge.length} liga\xE7\xE3o(\xF5es); leia sem aba para ver tudo.`
25403
+ );
25404
+ }
25043
25405
  const ialab = doc.meta?.ialab;
25044
25406
  if (ialab === null) lines.push("IA Lab: sem v\xEDnculo (gera\xE7\xF5es saem dos diamantes de quem roda).");
25045
25407
  else if (ialab && typeof ialab === "object") {
25046
25408
  const label = ["code", "name", "projectCode", "projectName"].map((k) => ialab[k]).find((v) => typeof v === "string");
25047
25409
  lines.push(`IA Lab: vinculado${label ? ` a ${String(label)}` : ""}.`);
25048
25410
  }
25049
- if (doc.meta?.guide) lines.push(`Guia: ${oneLine(doc.meta.guide)}`);
25411
+ if (doc.meta?.guide) lines.push(`Guia: ${oneLine2(doc.meta.guide)}`);
25412
+ if (tabbed) {
25413
+ lines.push("", `Abas (${index.tabs.length}), cart\xF5es de cada uma sem contar a se\xE7\xE3o (canvas_read_board com aba=<id> l\xEA uma s\xF3):`);
25414
+ for (const t of index.tabs) {
25415
+ const id = t.id === t.short ? "" : ` \xB7 id ${t.id}`;
25416
+ lines.push(`- aba=${t.short} \xB7 "${oneLine2(t.title, 60)}" \xB7 ${cardsText(t.cards)}${t.locked ? " \xB7 travada" : ""}${id}`);
25417
+ }
25418
+ if (index.loose) lines.push(`- aba=${AVULSOS_TAB} \xB7 ${cardsText(index.loose)} (n\xE3o moram em aba)`);
25419
+ }
25050
25420
  lines.push("", "N\xF3s (tamanho: medido = como est\xE1 na tela; gravado = piso, o cart\xE3o pode ser maior):");
25051
25421
  if (!nodes.length) lines.push("(vazio)");
25052
- for (const n of nodes) lines.push(...nodeLines(n, runs.get(n.id)));
25422
+ for (const n of nodes) {
25423
+ const home = tabbed ? part.home.get(n.id) : void 0;
25424
+ lines.push(...nodeLines(n, runs.get(n.id), home === void 0 ? void 0 : index.label(home)));
25425
+ }
25053
25426
  lines.push("", "Liga\xE7\xF5es:");
25054
25427
  if (!edges.length) lines.push("(nenhuma)");
25055
- for (const e of edges) lines.push(`- ${e.id}: ${e.source}.${e.sourcePort} \u2192 ${e.target}.${e.targetPort}`);
25428
+ for (const e of edges) {
25429
+ let note = "";
25430
+ if (scope && !(scope.has(e.source) && scope.has(e.target))) {
25431
+ const far = part.home.get(scope.has(e.source) ? e.target : e.source);
25432
+ const farInfo = far === void 0 ? void 0 : index.info(far);
25433
+ note = far === void 0 ? " \xB7 externo (o outro lado \xE9 um n\xF3 arquivado ou ausente)" : farInfo ? ` \xB7 externo (outro lado: aba "${oneLine2(farInfo.title, 40)}" aba=${farInfo.short})` : ` \xB7 externo (outro lado: ${AVULSOS_TAB})`;
25434
+ }
25435
+ lines.push(`- ${e.id}: ${e.source}.${e.sourcePort} \u2192 ${e.target}.${e.targetPort}${note}`);
25436
+ }
25056
25437
  return lines.join("\n");
25057
25438
  }
25058
25439
  function nodeDetail(node) {
@@ -25120,7 +25501,7 @@ function changesSince(envelopes, doc) {
25120
25501
  count.set(key, (count.get(key) ?? 0) + 1);
25121
25502
  }
25122
25503
  const parts = [...count].map(([t, n]) => `${n}\xD7 ${t}`);
25123
- const names = [...touchedNodes(envelopes)].map((id) => doc.nodes[id] ? `"${oneLine(nodeName(doc.nodes[id]), 40)}"` : `${id} (apagado)`).slice(0, 8);
25504
+ const names = [...touchedNodes(envelopes)].map((id) => doc.nodes[id] ? `"${oneLine2(nodeName(doc.nodes[id]), 40)}"` : `${id} (apagado)`).slice(0, 8);
25124
25505
  return `${envelopes.length} opera\xE7\xE3o(\xF5es) de outras pessoas desde a sua leitura (${parts.join(", ")})` + (names.length ? `; n\xF3s tocados: ${names.join(", ")}` : "");
25125
25506
  }
25126
25507
  function touchedNodes(envelopes) {
@@ -25151,7 +25532,7 @@ function mmss(t) {
25151
25532
  function nodeRef(doc, nodeId3) {
25152
25533
  const node = doc.nodes[nodeId3];
25153
25534
  const kind = nodeKind(node) ?? node.type;
25154
- return `"${oneLine(nodeName(node), 60)}" (${kind}, ${node.id})`;
25535
+ return `"${oneLine2(nodeName(node), 60)}" (${kind}, ${node.id})`;
25155
25536
  }
25156
25537
  function hashString(text) {
25157
25538
  let h1 = 3735928559;
@@ -25208,7 +25589,7 @@ function describeAnchor(anchor, doc) {
25208
25589
  const t = mmss(Number(anchor.t) || 0);
25209
25590
  if (kind === "montage") {
25210
25591
  const name = nodeName(node);
25211
- const title = name === KINDS.montage.title ? "" : ` "${oneLine(name, 60)}"`;
25592
+ const title = name === KINDS.montage.title ? "" : ` "${oneLine2(name, 60)}"`;
25212
25593
  return { text: `em ${t} da Montagem${title} (${node.id})`, nodeId: anchor.nodeId };
25213
25594
  }
25214
25595
  return { text: `em ${t} de ${ref}`, nodeId: anchor.nodeId };
@@ -25243,11 +25624,11 @@ function threadLines(t, doc) {
25243
25624
  const where = describeAnchor(t.anchor, doc);
25244
25625
  const state = t.resolvedAt ? " \xB7 RESOLVIDO" : "";
25245
25626
  const reacts = reactionsText(t.reactions);
25246
- const lines = t.deleted ? [`- ${t.id} \xB7 ${where.text}${state}`, " (coment\xE1rio raiz apagado: n\xE3o d\xE1 para responder nem resolver)"] : [`- ${t.id} \xB7 ${where.text} \xB7 ${who(t)}${state}${reacts ? ` \xB7 ${reacts}` : ""}`, ` pedido: ${oneLine(t.body ?? "", 400)}`];
25627
+ const lines = t.deleted ? [`- ${t.id} \xB7 ${where.text}${state}`, " (coment\xE1rio raiz apagado: n\xE3o d\xE1 para responder nem resolver)"] : [`- ${t.id} \xB7 ${where.text} \xB7 ${who(t)}${state}${reacts ? ` \xB7 ${reacts}` : ""}`, ` pedido: ${oneLine2(t.body ?? "", 400)}`];
25247
25628
  if (where.gone && !t.deleted) lines.push(` \u26A0 ALVO SUMIU: ${where.gone}. Responda com pergunta, n\xE3o resolva.`);
25248
25629
  const replies = Array.isArray(t.replies) ? t.replies : [];
25249
25630
  for (const r of replies.slice(-MAX_REPLIES)) {
25250
- lines.push(r.deleted ? " \u21B3 (resposta apagada)" : ` \u21B3 ${who(r)}: ${oneLine(r.body ?? "", 140)}`);
25631
+ lines.push(r.deleted ? " \u21B3 (resposta apagada)" : ` \u21B3 ${who(r)}: ${oneLine2(r.body ?? "", 140)}`);
25251
25632
  }
25252
25633
  if (replies.length > MAX_REPLIES) lines.splice(lines.length - MAX_REPLIES, 0, ` \u21B3 \u2026 ${replies.length - MAX_REPLIES} resposta(s) antes`);
25253
25634
  return lines;
@@ -25327,7 +25708,7 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25327
25708
  () => api.createComment(board_id, { body: text, parentId: comment_id, ...m?.length ? { mentions: m } : {} })
25328
25709
  );
25329
25710
  const open3 = thread?.resolvedAt ? "A thread est\xE1 resolvida; reabra com canvas_reopen_comment se precisar." : "A thread segue aberta.";
25330
- return `\u2705 Resposta ${newReplyId(thread)} em ${comment_id}: "${oneLine(text, 120)}". ${open3}`;
25711
+ return `\u2705 Resposta ${newReplyId(thread)} em ${comment_id}: "${oneLine2(text, 120)}". ${open3}`;
25331
25712
  })
25332
25713
  );
25333
25714
  server.registerTool(
@@ -25350,10 +25731,10 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25350
25731
  } catch (e) {
25351
25732
  const why = e instanceof Error ? e.message : String(e);
25352
25733
  throw new CanvasToolError(
25353
- `A resposta "${oneLine(done, 80)}" (${replyId}) foi postada em ${comment_id}, mas resolver falhou: ${why} N\xE3o repita canvas_resolve_comment (postaria a resposta de novo): avise a pessoa para resolver no Canvas.`
25734
+ `A resposta "${oneLine2(done, 80)}" (${replyId}) foi postada em ${comment_id}, mas resolver falhou: ${why} N\xE3o repita canvas_resolve_comment (postaria a resposta de novo): avise a pessoa para resolver no Canvas.`
25354
25735
  );
25355
25736
  }
25356
- return `\u2705 Resolvido ${comment_id}: "${oneLine(done, 120)}" (resposta ${replyId}).`;
25737
+ return `\u2705 Resolvido ${comment_id}: "${oneLine2(done, 120)}" (resposta ${replyId}).`;
25357
25738
  })
25358
25739
  );
25359
25740
  server.registerTool(
@@ -25367,7 +25748,7 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25367
25748
  async ({ board_id, comment_id, why }) => run(async () => {
25368
25749
  await onComment(() => api.reopenComment(board_id, comment_id));
25369
25750
  if (why) await onComment(() => api.createComment(board_id, { body: why, parentId: comment_id }));
25370
- return `\u2705 Reaberto ${comment_id}${why ? ` com a resposta "${oneLine(why, 120)}"` : ""}.`;
25751
+ return `\u2705 Reaberto ${comment_id}${why ? ` com a resposta "${oneLine2(why, 120)}"` : ""}.`;
25371
25752
  })
25372
25753
  );
25373
25754
  server.registerTool(
@@ -25422,6 +25803,13 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25422
25803
  );
25423
25804
  }
25424
25805
 
25806
+ // src/tools/canvas-aba-arg.ts
25807
+ var abaArg = (what) => external_exports.string().trim().min(1).max(128).optional().describe(what);
25808
+ var ABA_ADD_NODE = "Aba onde o cart\xE3o mora: o id da se\xE7\xE3o da aba ou o come\xE7o \xFAnico dele (o aba=\u2026 da leitura). Grava data.abaId e, sem position, o cart\xE3o nasce no fim do conte\xFAdo da aba (abaixo do cart\xE3o mais baixo, alinhado \xE0 esquerda do conte\xFAdo). data.abaId vale como aba; se os dois divergirem, recusa. Aba que n\xE3o existe ou que n\xE3o \xE9 aba \xE9 recusada.";
25809
+ var ABA_READ_BOARD = 'L\xEA s\xF3 uma aba: a se\xE7\xE3o, quem mora nela e os fios que tocam nesses n\xF3s (o fio que vai a outra aba vem marcado como externo). Aceita o id da se\xE7\xE3o, o come\xE7o \xFAnico dele (o aba=\u2026 da leitura) ou "avulsos". Sem aba, l\xEA o board inteiro.';
25810
+ var ABA_UPLOAD_MEDIA = "Aba do n\xF3 M\xEDdia novo (id da se\xE7\xE3o da aba ou o come\xE7o \xFAnico dele): grava data.abaId e p\xF5e o n\xF3 no fim do conte\xFAdo da aba, em vez de cair \xE0 direita de tudo, em Avulsos. S\xF3 vale com board_id e sem node_id (n\xF3 que j\xE1 existe n\xE3o muda de aba).";
25811
+ var ABA_ARRANGE_ROWS = 'Aba onde conferir a sobreposi\xE7\xE3o (id da se\xE7\xE3o ou o come\xE7o \xFAnico dele): s\xF3 os cart\xF5es dela contam como "de fora". Sem aba, vale a aba dos n\xF3s arrumados, quando \xE9 uma s\xF3; sen\xE3o, o board inteiro. N\xE3o muda a aba de nenhum n\xF3.';
25812
+
25425
25813
  // src/tools/canvas-layout.ts
25426
25814
  var nodeIdArg = external_exports.string().min(1).max(128);
25427
25815
  var width = external_exports.number().finite().min(40).max(2e4);
@@ -25461,8 +25849,24 @@ function layoutOps(doc, a) {
25461
25849
  }
25462
25850
  return ops;
25463
25851
  }
25464
- function report(doc, a) {
25465
- const name = (id) => `"${oneLine(nodeName(doc.nodes[id]), 36)}"`;
25852
+ function scopeFor(doc, rows, aba) {
25853
+ const tab = aba ? resolveTab(doc, aba) : void 0;
25854
+ if (!hasTabs(doc)) return void 0;
25855
+ const arranged = rows.flatMap((r) => [...r.title ? [r.title] : [], ...r.left, ...r.right]);
25856
+ const homes = new Set(arranged.map((id) => homeTabOf(doc, id)));
25857
+ const only = homes.size === 1 ? [...homes][0] : null;
25858
+ const chosen = tab ?? (only && only !== AVULSOS_TAB ? only : void 0);
25859
+ if (chosen === void 0) return void 0;
25860
+ const info = tabIndex(doc).info(chosen);
25861
+ return {
25862
+ ids: membersOfTab(doc, chosen),
25863
+ title: info.title,
25864
+ short: info.short,
25865
+ foreign: tab === void 0 ? [] : arranged.filter((id) => homeTabOf(doc, id) !== tab)
25866
+ };
25867
+ }
25868
+ function report(doc, a, scope) {
25869
+ const name = (id) => `"${oneLine2(nodeName(doc.nodes[id]), 36)}"`;
25466
25870
  const lines = [];
25467
25871
  const unmeasured = a.placed.filter((p) => p.source === "gravado");
25468
25872
  const estimated = a.placed.filter((p) => p.source === "medido" && p.estimated);
@@ -25484,9 +25888,17 @@ function report(doc, a) {
25484
25888
  lines.push("Todos os n\xF3s arrumados t\xEAm medida da tela.");
25485
25889
  }
25486
25890
  const pairs = (list) => list.map(([x, y]) => `${name(x)} \xD7 ${name(y)}`).join("; ");
25891
+ const where = scope ? ` (s\xF3 na aba "${oneLine2(scope.title, 40)}" aba=${scope.short})` : "";
25487
25892
  lines.push(
25488
- 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."
25893
+ a.inner.length || a.outer.length ? `Sobreposi\xE7\xF5es com os tamanhos usados${where}: ${pairs([...a.inner, ...a.outer])}.` : `Sobreposi\xE7\xF5es com os tamanhos usados${where}: nenhuma.`
25489
25894
  );
25895
+ if (scope?.foreign.length) {
25896
+ const n = scope.foreign.length;
25897
+ const names = scope.foreign.slice(0, 5).map(name).join(", ") + (n > 5 ? ` e mais ${n - 5}` : "");
25898
+ lines.push(
25899
+ `Aten\xE7\xE3o: ${names} ${n === 1 ? "mora" : "moram"} em outra aba que n\xE3o a pedida; o arranjo grava s\xF3 posi\xE7\xE3o e n\xE3o troca a aba de ningu\xE9m (data.abaId).`
25900
+ );
25901
+ }
25490
25902
  lines.push('Caixas ([papel] id "nome" @(x,y) largura\xD7altura usada, origem da altura):');
25491
25903
  const rows = /* @__PURE__ */ new Map();
25492
25904
  for (const p of a.placed) {
@@ -25510,10 +25922,11 @@ function registerCanvasLayoutTools(server, deps) {
25510
25922
  "canvas_arrange_rows",
25511
25923
  {
25512
25924
  title: "Canvas: arrumar em linhas",
25513
- 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.",
25925
+ 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 e de v\xEDdeo na largura do aspect_ratio do dado, na altura de cria\xE7\xE3o 516 (16:9 917, 9:16 290, 1:1 516), 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 da mesma aba (aba, ou a dos n\xF3s arrumados), n\xE3o grava nada e sugere uma origem livre. dry_run: true s\xF3 calcula.",
25514
25926
  inputSchema: {
25515
25927
  board_id: external_exports.string().uuid(),
25516
25928
  origin: external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() }).describe("Canto de cima \xE0 esquerda da \xE1rea."),
25929
+ aba: abaArg(ABA_ARRANGE_ROWS),
25517
25930
  rows: external_exports.array(
25518
25931
  external_exports.object({
25519
25932
  title: nodeIdArg.optional().describe("N\xF3 do t\xEDtulo da linha (Texto livre)."),
@@ -25530,7 +25943,7 @@ function registerCanvasLayoutTools(server, deps) {
25530
25943
  reference: width.optional(),
25531
25944
  frame: width.optional(),
25532
25945
  title: width.optional()
25533
- }).strict().optional().describe("Troca larguras da conven\xE7\xE3o (prompt 1024, image 560, video 560, reference 320, frame 320, title 1024)."),
25946
+ }).strict().optional().describe("Troca larguras da conven\xE7\xE3o (prompt 1024, reference 320, frame 320, title 1024). image e video, sem troca, seguem o aspect_ratio do dado na altura de cria\xE7\xE3o 516 (16:9 917, 9:16 290, 1:1 516); com n\xFAmero, vale o n\xFAmero."),
25534
25947
  dry_run: external_exports.boolean().optional().describe("true: s\xF3 calcula e mostra, sem gravar.")
25535
25948
  },
25536
25949
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
@@ -25538,19 +25951,20 @@ function registerCanvasLayoutTools(server, deps) {
25538
25951
  async (args) => run(async () => {
25539
25952
  const rows = args.rows.map((r) => ({ title: r.title, left: r.left ?? [], right: r.right ?? [] }));
25540
25953
  const { generator, ...own } = args.widths ?? {};
25541
- const widths = { ...DEFAULT_WIDTHS, ...generator ? { image: generator, video: generator } : {}, ...own };
25954
+ const widths = { ...generator ? { image: generator, video: generator } : {}, ...own };
25542
25955
  const gap = args.gap ?? DEFAULT_GAP;
25543
25956
  const plan = (doc) => {
25544
25957
  checkRows(doc, rows);
25545
- const a = arrangeRows(doc, rows, args.origin, gap, widths);
25546
- return { a, ops: layoutOps(doc, a), text: report(doc, a) };
25958
+ const scope = scopeFor(doc, rows, args.aba);
25959
+ const a = arrangeRows(doc, rows, args.origin, gap, widths, scope?.ids);
25960
+ return { a, scoped: scope !== void 0, ops: layoutOps(doc, a), text: report(doc, a, scope) };
25547
25961
  };
25548
25962
  const count = (ops) => new Set(ops.map((o) => o.type === "node.set" ? o.nodeId : "")).size;
25549
- 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.`;
25963
+ const blocked = (a, scoped) => `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${scoped ? " da aba" : ""} nessa faixa), ou tire esses n\xF3s do caminho.`;
25550
25964
  if (args.dry_run) {
25551
- const { a, ops, text: text2 } = plan((await api.snapshot(args.board_id)).doc);
25965
+ const { a, scoped, ops, text: text2 } = plan((await api.snapshot(args.board_id)).doc);
25552
25966
  const head = `Pr\xE9via, nada gravado: ${ops.length ? `${count(ops)} n\xF3(s) mudariam de lugar ou largura` : "tudo j\xE1 est\xE1 assim"}.`;
25553
- return [head, ...a.outer.length ? [blocked(a)] : [], text2].join("\n");
25967
+ return [head, ...a.outer.length ? [blocked(a, scoped)] : [], text2].join("\n");
25554
25968
  }
25555
25969
  let text = "";
25556
25970
  const done = await edit(
@@ -25558,7 +25972,7 @@ function registerCanvasLayoutTools(server, deps) {
25558
25972
  async (doc) => {
25559
25973
  const p = plan(doc);
25560
25974
  text = p.text;
25561
- if (p.a.outer.length) throw new CanvasToolError(`${blocked(p.a)}
25975
+ if (p.a.outer.length) throw new CanvasToolError(`${blocked(p.a, p.scoped)}
25562
25976
  ${p.text}`);
25563
25977
  return {
25564
25978
  ops: p.ops,
@@ -25654,7 +26068,7 @@ function needMediaNode(doc, nodeId3) {
25654
26068
  if (!node) throw new CanvasToolError(`N\xF3 ${nodeId3} n\xE3o existe no board. Releia com canvas_read_board.`);
25655
26069
  if (nodeKind(node) !== "media") {
25656
26070
  throw new CanvasToolError(
25657
- `O n\xF3 ${nodeId3} ("${oneLine(nodeName(node), 40)}") \xE9 ${nodeKind(node) ?? node.type}, n\xE3o M\xEDdia. Passe o id de um n\xF3 media, ou omita node_id para criar um n\xF3 M\xEDdia novo.`
26071
+ `O n\xF3 ${nodeId3} ("${oneLine2(nodeName(node), 40)}") \xE9 ${nodeKind(node) ?? node.type}, n\xE3o M\xEDdia. Passe o id de um n\xF3 media, ou omita node_id para criar um n\xF3 M\xEDdia novo.`
25658
26072
  );
25659
26073
  }
25660
26074
  return node;
@@ -25689,20 +26103,26 @@ function registerCanvasMediaTools(server, deps) {
25689
26103
  "canvas_upload_media",
25690
26104
  {
25691
26105
  title: "Canvas: subir arquivo",
25692
- description: "Sobe um arquivo (caminho local ou URL) pela rota de arquivos da BePlus e devolve o item pronto: url, mime, bytes, dura\xE7\xE3o/largura/altura quando \xE9 m\xEDdia, media_item (data.items do n\xF3 M\xEDdia), sequence_asset e libraryItemId (clipe da timeline da Montagem). Com board_id grava no n\xF3 M\xEDdia node_id, ou cria um n\xF3 M\xEDdia novo sem node_id; a\xED exige canvas_read_board nesta sess\xE3o. Teto de 95 MB. N\xE3o gera m\xEDdia nem gasta diamantes.",
26106
+ description: "Sobe um arquivo (caminho local ou URL) pela rota de arquivos da BePlus e devolve o item pronto: url, mime, bytes, dura\xE7\xE3o/largura/altura quando \xE9 m\xEDdia, media_item (data.items do n\xF3 M\xEDdia), sequence_asset e libraryItemId (clipe da timeline da Montagem). Com board_id grava no n\xF3 M\xEDdia node_id, ou cria um n\xF3 M\xEDdia novo sem node_id (com aba, ele nasce no fim do conte\xFAdo da aba e grava data.abaId); a\xED exige canvas_read_board nesta sess\xE3o. Teto de 95 MB. N\xE3o gera m\xEDdia nem gasta diamantes.",
25693
26107
  inputSchema: {
25694
26108
  source: external_exports.string().trim().min(1).max(2e3).describe("Caminho local (/\u2026, ~/\u2026, file://) ou URL http(s)."),
25695
26109
  name: external_exports.string().trim().min(1).max(120).optional().describe("Nome do arquivo no Canvas (a extens\xE3o \xE9 mantida)."),
25696
26110
  board_id: external_exports.string().uuid().optional().describe("Board onde gravar o item (opcional)."),
25697
26111
  node_id: external_exports.string().min(1).max(128).optional().describe("N\xF3 M\xEDdia que recebe o item. Sem ele, cria um n\xF3 M\xEDdia novo."),
26112
+ aba: abaArg(ABA_UPLOAD_MEDIA),
25698
26113
  label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do n\xF3 M\xEDdia novo (padr\xE3o: nome do arquivo)."),
25699
26114
  replace: external_exports.boolean().optional().describe("true troca a lista do n\xF3 por este arquivo; false acrescenta. Padr\xE3o: a regra do Canvas (n\xF3 de um arquivo s\xF3, ligado, troca).")
25700
26115
  },
25701
26116
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
25702
26117
  },
25703
26118
  async (args) => run(async () => {
25704
- if (!args.board_id && (args.node_id || args.label || args.replace !== void 0)) {
25705
- throw new CanvasToolError("node_id, label e replace s\xF3 valem com board_id.");
26119
+ if (!args.board_id && (args.node_id || args.label || args.replace !== void 0 || args.aba)) {
26120
+ throw new CanvasToolError("node_id, label, replace e aba s\xF3 valem com board_id.");
26121
+ }
26122
+ if (args.aba && args.node_id) {
26123
+ throw new CanvasToolError(
26124
+ "aba s\xF3 vale quando o upload cria um n\xF3 M\xEDdia novo (sem node_id): o n\xF3 que j\xE1 existe n\xE3o muda de aba por aqui."
26125
+ );
25706
26126
  }
25707
26127
  if (args.board_id) {
25708
26128
  if (!seen.has(args.board_id)) {
@@ -25710,12 +26130,13 @@ function registerCanvasMediaTools(server, deps) {
25710
26130
  }
25711
26131
  const snap = await api.snapshot(args.board_id);
25712
26132
  if (snap.role === "viewer") throw new CanvasToolError("Voc\xEA \xE9 s\xF3 leitor neste board: pe\xE7a ao dono para te dar papel de editor.");
26133
+ if (args.aba) resolveTab(snap.doc, args.aba);
25713
26134
  if (args.node_id) {
25714
26135
  const node = needMediaNode(snap.doc, args.node_id);
25715
26136
  const replacing = args.replace ?? holdsOne(snap.doc, node);
25716
26137
  if (!replacing && itemsOf(node).length >= MAX_MEDIA_ITEMS) {
25717
26138
  throw new CanvasToolError(
25718
- `O n\xF3 "${oneLine(nodeName(node), 40)}" j\xE1 tem ${MAX_MEDIA_ITEMS} arquivos (teto do Canvas). Omita node_id para criar outro n\xF3 M\xEDdia.`
26139
+ `O n\xF3 "${oneLine2(nodeName(node), 40)}" j\xE1 tem ${MAX_MEDIA_ITEMS} arquivos (teto do Canvas). Omita node_id para criar outro n\xF3 M\xEDdia.`
25719
26140
  );
25720
26141
  }
25721
26142
  }
@@ -25749,14 +26170,21 @@ URL: ${u.url}`;
25749
26170
  return {
25750
26171
  ops: [{ type: "node.set", nodeId: node.id, path: "data.items", value: setValue(items) }],
25751
26172
  targets: [node.id],
25752
- done: `N\xF3 M\xEDdia "${oneLine(nodeName(node), 40)}" (${node.id}): ` + (replacing ? `arquivo trocado (${before} \u2192 1)` : `arquivo acrescentado, agora com ${items.length}`)
26173
+ done: `N\xF3 M\xEDdia "${oneLine2(nodeName(node), 40)}" (${node.id}): ` + (replacing ? `arquivo trocado (${before} \u2192 1)` : `arquivo acrescentado, agora com ${items.length}`)
25753
26174
  };
25754
26175
  }
25755
26176
  const spec = KINDS.media;
26177
+ const tab = args.aba ? resolveTab(doc, args.aba) : void 0;
25756
26178
  const created = makeNode({
25757
26179
  type: WORKFLOW_NODE_TYPE,
25758
- data: { ...spec.data, [KIND_FIELD]: "media", label: args.label ?? u.name, items: [item] },
25759
- position: placeFor(doc, spec.size),
26180
+ data: {
26181
+ ...spec.data,
26182
+ [KIND_FIELD]: "media",
26183
+ label: args.label ?? u.name,
26184
+ items: [item],
26185
+ ...tab === void 0 ? {} : { [TAB_ID_FIELD]: tab }
26186
+ },
26187
+ position: placeFor(doc, spec.size, void 0, "right", tab),
25760
26188
  size: spec.size,
25761
26189
  zIndex: Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1,
25762
26190
  createdBy: await api.me()
@@ -25765,7 +26193,7 @@ URL: ${u.url}`;
25765
26193
  return {
25766
26194
  ops: [{ type: "node.add", nodeId: created.id, node: created }],
25767
26195
  targets: [],
25768
- done: `N\xF3 M\xEDdia "${oneLine(nodeName(created), 40)}" criado: ${created.id}`
26196
+ done: `N\xF3 M\xEDdia "${oneLine2(nodeName(created), 40)}" criado: ${created.id}.${placementNote(doc, created)}`
25769
26197
  };
25770
26198
  });
25771
26199
  } catch (e) {
@@ -26145,6 +26573,7 @@ function isLockedSection(node) {
26145
26573
  return node !== void 0 && nodeKind(node) === "section" && node.data[SECTION_LOCK_FIELD] === true;
26146
26574
  }
26147
26575
  var LAYOUT_PATH = /^(position|size)(\.|$)/;
26576
+ var STAMP_PATH = `data.${TAB_ID_FIELD}`;
26148
26577
  function rectOf(node, withMeasured) {
26149
26578
  const h = withMeasured ? Math.max(node.size.h, readMeasured(node)?.h ?? 0) : node.size.h;
26150
26579
  return { x: node.position.x, y: node.position.y, w: node.size.w, h };
@@ -26152,14 +26581,14 @@ function rectOf(node, withMeasured) {
26152
26581
  function contains(outer, inner) {
26153
26582
  return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.w <= outer.x + outer.w && inner.y + inner.h <= outer.y + outer.h;
26154
26583
  }
26155
- function flatten(ops) {
26156
- return ops.flatMap((op) => op.type === "batch" ? flatten(op.ops) : [op]);
26584
+ function flatten2(ops) {
26585
+ return ops.flatMap((op) => op.type === "batch" ? flatten2(op.ops) : [op]);
26157
26586
  }
26158
26587
  function lockViolation(doc, ops) {
26159
26588
  const locked = Object.values(doc.nodes).filter(isLockedSection);
26160
26589
  if (!locked.length) return null;
26161
- const name = (node) => `"${oneLine(nodeName(node), 40)}"`;
26162
- const flat = flatten(ops);
26590
+ const name = (node) => `"${oneLine2(tabTitle(node), 40)}"`;
26591
+ const flat = flatten2(ops);
26163
26592
  for (const op of flat) {
26164
26593
  if (op.type !== "node.set" && op.type !== "node.remove") continue;
26165
26594
  const node = doc.nodes[op.nodeId];
@@ -26167,18 +26596,30 @@ function lockViolation(doc, ops) {
26167
26596
  if (op.type === "node.remove") return `${name(node)}: ${LOCKED_TEXT}. Nada foi apagado.`;
26168
26597
  if (LAYOUT_PATH.test(op.path)) return `${name(node)}: ${LOCKED_TEXT}. Nada foi aplicado.`;
26169
26598
  }
26170
- const moved = /* @__PURE__ */ new Set();
26599
+ const home = partitionOf(doc).home;
26600
+ const placed = /* @__PURE__ */ new Set();
26601
+ const restamped = /* @__PURE__ */ new Set();
26171
26602
  for (const op of flat) {
26172
- if (op.type !== "node.set" || !LAYOUT_PATH.test(op.path)) continue;
26603
+ if (op.type !== "node.set") continue;
26173
26604
  const node = doc.nodes[op.nodeId];
26174
- if (node !== void 0 && nodeKind(node) !== "section") moved.add(op.nodeId);
26605
+ if (node === void 0 || nodeKind(node) === "section") continue;
26606
+ if (LAYOUT_PATH.test(op.path)) {
26607
+ placed.add(op.nodeId);
26608
+ } else if (op.path === STAMP_PATH) {
26609
+ const sameHome = op.value.kind === "value" && op.value.value === home.get(op.nodeId);
26610
+ if (!sameHome) restamped.add(op.nodeId);
26611
+ }
26175
26612
  }
26613
+ const moved = /* @__PURE__ */ new Set([...placed, ...restamped]);
26176
26614
  if (moved.size < 2) return null;
26177
26615
  for (const section of locked) {
26178
- const outer = rectOf(section, false);
26179
- const inside = [...moved].filter((id) => contains(outer, rectOf(doc.nodes[id], true)));
26616
+ const inside = isTabSection(section) ? [...moved].filter((id) => home.get(id) === section.id) : (() => {
26617
+ const outer = rectOf(section, false);
26618
+ return [...placed].filter((id) => contains(outer, rectOf(doc.nodes[id], true)));
26619
+ })();
26180
26620
  if (inside.length >= 2) {
26181
- return `${name(section)}: ${LOCKED_TEXT}. Mudar ${inside.length} n\xF3s de dentro dela num pedido s\xF3 n\xE3o vale (um por vez segue valendo). Nada foi aplicado.`;
26621
+ const stamp = inside.some((id) => restamped.has(id)) ? " Trocar a aba (data.abaId) tamb\xE9m conta como mexer na aba." : "";
26622
+ return `${name(section)}: ${LOCKED_TEXT}. Mudar ${inside.length} n\xF3s de dentro dela num pedido s\xF3 n\xE3o vale (um por vez segue valendo).${stamp} Nada foi aplicado.`;
26182
26623
  }
26183
26624
  }
26184
26625
  return null;
@@ -26347,7 +26788,7 @@ var point = external_exports.object({ x: external_exports.number().finite(), y:
26347
26788
  var LABEL_TEXT_SIZES = ["titulo", "subtitulo", "corpo"];
26348
26789
  var dim = external_exports.number().finite().min(16).max(2e4);
26349
26790
  var sizeArg = external_exports.union([external_exports.object({ w: dim.optional(), h: dim.optional() }).strict(), external_exports.enum(LABEL_TEXT_SIZES)]).describe(
26350
- '{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.'
26791
+ '{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. Ao criar um Texto livre sem w, a largura vem do tamanho do texto: titulo 2000, subtitulo 1200, corpo 320.'
26351
26792
  );
26352
26793
  var isLayoutOp = (op) => op.type === "batch" ? op.ops.every(isLayoutOp) : op.type === "node.set" && /^(position\.[xy]|size\.[wh])$/.test(op.path);
26353
26794
  function textSizeFor(kind, size) {
@@ -26362,6 +26803,10 @@ function boxOf(size) {
26362
26803
  if (size.w === void 0 && size.h === void 0) throw new CanvasToolError("size {w, h}: informe w, h ou os dois.");
26363
26804
  return size;
26364
26805
  }
26806
+ function birthBox(kind, spec, data, asked) {
26807
+ const w = asked?.w ?? (kind === "label" ? labelBirthWidth(data.size) : isGeneratorKind(kind) ? generatorBirthWidth(kind, data.aspect_ratio) : spec.size.w);
26808
+ return { w, h: asked?.h ?? spec.size.h };
26809
+ }
26365
26810
  function registerCanvasTools(server, client, cfg) {
26366
26811
  const api = new CanvasApi(client, cfg);
26367
26812
  const seen = /* @__PURE__ */ new Map();
@@ -26383,7 +26828,7 @@ function registerCanvasTools(server, client, cfg) {
26383
26828
  return fail(e);
26384
26829
  }
26385
26830
  }
26386
- async function edit(id, build, opts = {}) {
26831
+ async function edit(id, build2, opts = {}) {
26387
26832
  const last = seen.get(id);
26388
26833
  if (last === void 0 && !opts.layoutOnly) {
26389
26834
  throw new CanvasToolError("Leia o board com canvas_read_board antes de editar (a leitura vale para a sess\xE3o).");
@@ -26391,11 +26836,13 @@ function registerCanvasTools(server, client, cfg) {
26391
26836
  const snap = await api.snapshot(id);
26392
26837
  if (snap.role === "viewer") throw new CanvasToolError("Voc\xEA \xE9 s\xF3 leitor neste board: pe\xE7a ao dono para te dar papel de editor.");
26393
26838
  const doc = snap.doc;
26394
- const { ops, done, targets } = await build(doc, snap);
26839
+ const { ops, done, targets } = await build2(doc, snap);
26395
26840
  if (!ops.length) return `Nada a mudar. ${done}`;
26396
26841
  if (opts.layoutOnly && !ops.every(isLayoutOp)) throw new Error("layout_only com opera\xE7\xE3o fora de posi\xE7\xE3o/tamanho");
26397
26842
  const locked = lockViolation(doc, ops);
26398
26843
  if (locked) throw new CanvasToolError(locked);
26844
+ const badAba = abaIdViolation(doc, ops);
26845
+ if (badAba) throw new CanvasToolError(`${badAba} Nada foi aplicado.`);
26399
26846
  let note = "";
26400
26847
  if (!opts.layoutOnly && last !== void 0 && snap.seq > last) {
26401
26848
  const since = await api.opsAfter(id, last);
@@ -26409,7 +26856,7 @@ function registerCanvasTools(server, client, cfg) {
26409
26856
  const clash = [...touchedNodes(theirs)].filter((n) => targets.includes(n));
26410
26857
  if (clash.length) {
26411
26858
  seen.delete(id);
26412
- const names = clash.map((n) => doc.nodes[n] ? `"${oneLine(nodeName(doc.nodes[n]), 40)}"` : n).join(", ");
26859
+ const names = clash.map((n) => doc.nodes[n] ? `"${oneLine2(nodeName(doc.nodes[n]), 40)}"` : n).join(", ");
26413
26860
  throw new CanvasToolError(
26414
26861
  `Outra pessoa mexeu em ${names} depois da sua leitura (seq ${last} \u2192 ${snap.seq}). Nada foi aplicado: releia com canvas_read_board e refa\xE7a a edi\xE7\xE3o em cima do estado atual.`
26415
26862
  );
@@ -26506,15 +26953,17 @@ ${lines.join("\n")}${note}`;
26506
26953
  }
26507
26954
  return { ops, warn };
26508
26955
  }
26509
- function placeFor(doc, size, near, side = "right") {
26956
+ function placeFor(doc, size, near, side = "right", tab) {
26510
26957
  if (near) {
26511
26958
  const x = side === "right" ? near.position.x + near.size.w + GAP : near.position.x - size.w - GAP;
26512
26959
  let y = near.position.y;
26513
- const nodes2 = Object.values(doc.nodes);
26960
+ const scope = tab === void 0 ? null : membersOfTab(doc, tab);
26961
+ const nodes2 = Object.values(doc.nodes).filter((n) => scope === null || scope.has(n.id) && nodeKind(n) !== "section");
26514
26962
  const hits = (yy) => nodes2.some((n) => x < n.position.x + n.size.w && x + size.w > n.position.x && yy < n.position.y + n.size.h && yy + size.h > n.position.y);
26515
26963
  for (let i = 0; i < 50 && hits(y); i++) y += GAP;
26516
26964
  return { x, y };
26517
26965
  }
26966
+ if (tab !== void 0 && tab !== AVULSOS_TAB) return endOfTabContent(doc, tab, size);
26518
26967
  const nodes = Object.values(doc.nodes).filter((n) => nodeKind(n) !== "section");
26519
26968
  if (!nodes.length) return { x: 0, y: 0 };
26520
26969
  return {
@@ -26570,7 +27019,7 @@ ${lines.join("\n")}${note}`;
26570
27019
  for (const c of changed) after.nodes[c.node.id] = { ...c.node, data: c.data };
26571
27020
  for (const e of newEdges) after.edges[e.id] = e;
26572
27021
  const sync = mentionEdgeOps(after, prompts.map((c) => c.node.id), await api.me());
26573
- const name = (id) => after.nodes[id] ? `"${oneLine(nodeName(after.nodes[id]), 40)}"` : id;
27022
+ const name = (id) => after.nodes[id] ? `"${oneLine2(nodeName(after.nodes[id]), 40)}"` : id;
26574
27023
  return { ops: sync.ops, note: describeMentionSync(sync, name) };
26575
27024
  }
26576
27025
  server.registerTool(
@@ -26591,17 +27040,19 @@ ${lines.join("\n")}${note}`;
26591
27040
  "canvas_read_board",
26592
27041
  {
26593
27042
  title: "Canvas: ler board",
26594
- 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.',
27043
+ 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. Cada cart\xE3o diz a aba (aba=<id curto> ou aba=avulsos), o topo conta os cart\xF5es de cada aba e aba l\xEA uma aba s\xF3.',
26595
27044
  inputSchema: {
26596
27045
  board_id: boardId3,
26597
- node_ids: external_exports.array(nodeIdArg2).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
27046
+ node_ids: external_exports.array(nodeIdArg2).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados)."),
27047
+ aba: abaArg(ABA_READ_BOARD)
26598
27048
  },
26599
27049
  annotations: { readOnlyHint: true, openWorldHint: false }
26600
27050
  },
26601
- async ({ board_id, node_ids }) => run(async () => {
27051
+ async ({ board_id, node_ids, aba }) => run(async () => {
26602
27052
  const snap = await api.snapshot(board_id);
27053
+ const tab = aba ? resolveTab(snap.doc, aba, { allowAvulsos: true }) : void 0;
26603
27054
  seen.set(board_id, snap.seq);
26604
- const parts = [boardSummary(snap)];
27055
+ const parts = [boardSummary(snap, { tab })];
26605
27056
  if (node_ids?.length) {
26606
27057
  parts.push("", "Detalhe:");
26607
27058
  for (const n of node_ids) parts.push(snap.doc.nodes[n] ? nodeDetail(snap.doc.nodes[n]) : `${n}: n\xE3o existe`);
@@ -26661,7 +27112,7 @@ ${lines.join("\n")}${note}`;
26661
27112
  const hash = createHash("sha256").update(json2).digest("hex").slice(0, 12);
26662
27113
  const where = `data${segments.length ? `.${segments.join(".")}` : ""}`;
26663
27114
  const at = readMeasured(node)?.at;
26664
- 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}`;
27115
+ const head = `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${oneLine2(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}`;
26665
27116
  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."}`;
26666
27117
  return `${head}
26667
27118
  ${part}
@@ -26722,12 +27173,13 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
26722
27173
  "canvas_add_node",
26723
27174
  {
26724
27175
  title: "Canvas: adicionar n\xF3",
26725
- 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,
27176
+ 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 (com aba, no fim do conte\xFAdo da aba); aba grava data.abaId. Texto livre (label) sem size {w} nasce na largura do tamanho do texto: titulo 2000, subtitulo 1200, corpo 320. Prompt sem size nasce com 1024 \xD7 516; gerador de imagem ou v\xEDdeo, com altura 516 e a largura do aspect_ratio (16:9 917, 9:16 290, 1:1 516). inputs/outputs j\xE1 ligam o n\xF3 no mesmo passo. ` + MENTION_NOTE + MONTAGE_NOTE,
26726
27177
  inputSchema: {
26727
27178
  board_id: boardId3,
26728
27179
  kind: external_exports.string().min(1).max(40),
26729
27180
  label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do cart\xE3o (data.label)."),
26730
27181
  data: external_exports.record(external_exports.unknown()).optional().describe("Campos do data. Ex.: narration {blocks:[{id,text}]}, text {text}, prompt {template}."),
27182
+ aba: abaArg(ABA_ADD_NODE),
26731
27183
  position: point.optional(),
26732
27184
  size: sizeArg.optional(),
26733
27185
  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."),
@@ -26742,14 +27194,21 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
26742
27194
  const me = await api.me();
26743
27195
  const data = { ...spec.data, ...parseData(args.data), [KIND_FIELD]: args.kind };
26744
27196
  if (args.label) data.label = args.label;
26745
- let box = spec.size;
26746
27197
  if (typeof args.size === "string") data.size = textSizeFor(args.kind, args.size);
26747
- else if (args.size) {
26748
- const b = boxOf(args.size);
26749
- box = { w: b.w ?? spec.size.w, h: b.h ?? spec.size.h };
27198
+ const box = birthBox(args.kind, spec, data, args.size && typeof args.size !== "string" ? boxOf(args.size) : void 0);
27199
+ const byParam = args.aba ? resolveTab(doc, args.aba) : void 0;
27200
+ const written = data[TAB_ID_FIELD];
27201
+ const byData = typeof written === "string" && written !== "" ? resolveTab(doc, written) : void 0;
27202
+ if (byParam !== void 0 && byData !== void 0 && byParam !== byData) {
27203
+ throw new CanvasToolError(
27204
+ `aba ("${tabTitle(doc.nodes[byParam])}") e data.abaId ("${tabTitle(doc.nodes[byData])}") apontam para abas diferentes: mande s\xF3 aba.`
27205
+ );
26750
27206
  }
27207
+ const tab = byParam ?? byData;
27208
+ if (tab !== void 0) data[TAB_ID_FIELD] = tab;
26751
27209
  const near = args.inputs?.[0] ? doc.nodes[args.inputs[0].from] : args.outputs?.[0] ? doc.nodes[args.outputs[0].to] : void 0;
26752
- const position = args.position ?? placeFor(doc, box, near, args.inputs?.[0] ? "right" : "left");
27210
+ const nearTab = near ? homeTabOf(doc, near.id) ?? void 0 : void 0;
27211
+ const position = args.position ?? placeFor(doc, box, tab === void 0 ? near : void 0, args.inputs?.[0] ? "right" : "left", tab ?? nearTab);
26753
27212
  const zIndex = args.kind === "section" ? SECTION_Z : Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1;
26754
27213
  const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: box, zIndex, createdBy: me });
26755
27214
  const links = [
@@ -26770,7 +27229,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
26770
27229
  return {
26771
27230
  ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops, ...mention.ops],
26772
27231
  targets: links.map((l) => l.source === node.id ? l.target : l.source),
26773
- 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}`
27232
+ done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${placementNote(doc, node)}${edges.warn}${mention.note}`
26774
27233
  };
26775
27234
  })
26776
27235
  )
@@ -26844,7 +27303,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
26844
27303
  });
26845
27304
  const mention = args.layout_only ? { ops: [], note: "" } : await mentionOps(doc, changes.map((c) => ({ node: c.node, data: c.data, keys: c.dataKeys })));
26846
27305
  const touched = changes.filter((c) => c.ops.length);
26847
- const list = touched.slice(0, 12).map((c) => `"${oneLine(nodeName(c.node), 30)}" (${c.keys.join(", ")})`).join("; ");
27306
+ const list = touched.slice(0, 12).map((c) => `"${oneLine2(nodeName(c.node), 30)}" (${c.keys.join(", ")})`).join("; ");
26848
27307
  return {
26849
27308
  ops: [...changes.flatMap((c) => c.ops), ...mention.ops],
26850
27309
  targets: ids,
@@ -26947,7 +27406,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
26947
27406
  return {
26948
27407
  ops,
26949
27408
  targets: [node.id],
26950
- done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${written} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "") + mention.note
27409
+ done: `N\xF3 "${oneLine2(nodeName(node), 40)}" (${node.id}): ${written} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "") + mention.note
26951
27410
  };
26952
27411
  },
26953
27412
  { split: true }
@@ -27017,7 +27476,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
27017
27476
  ops.push(op);
27018
27477
  }
27019
27478
  }
27020
- const names = nodeIds.map((n) => `"${oneLine(nodeName(doc.nodes[n]), 40)}"`);
27479
+ const names = nodeIds.map((n) => `"${oneLine2(nodeName(doc.nodes[n]), 40)}"`);
27021
27480
  return {
27022
27481
  ops,
27023
27482
  targets: nodeIds,
@@ -27099,7 +27558,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
27099
27558
  import { createRequire } from "module";
27100
27559
  var require2 = createRequire(import.meta.url);
27101
27560
  function readVersion() {
27102
- if ("0.36.0") return "0.36.0";
27561
+ if ("0.37.1") return "0.37.1";
27103
27562
  try {
27104
27563
  const pkg = require2("../package.json");
27105
27564
  return pkg.version ?? "0.0.0-unknown";
@@ -27115,6 +27574,7 @@ var SERVER_INSTRUCTIONS = [
27115
27574
  '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.',
27116
27575
  "Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
27117
27576
  "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 canvas_* n\xE3o geram m\xEDdia, exceto canvas_run: roda n\xF3s pela aba do Canvas aberta do usu\xE1rio e gasta diamantes; max_diamonds \xE9 o teto que o usu\xE1rio autorizou (dry_run s\xF3 estima). D\xEA nome (label) aos n\xF3s, n\xE3o invente tipos nem portas, e confirme antes de canvas_delete.",
27577
+ "Cada aba \xE9 um plano: ao criar, passe aba (id da se\xE7\xE3o) em canvas_add_node e canvas_upload_media, sen\xE3o o cart\xE3o pode cair em Avulsos. canvas_read_board com aba l\xEA uma aba s\xF3.",
27118
27578
  'Mostrar algo ao usu\xE1rio ("olha aqui"): canvas_focus leva a aba do Canvas que ele est\xE1 olhando at\xE9 os n\xF3s e os seleciona uma vez, sem prender a aba, sem gasto e sem escrita no board. follow: true prende a aba na \xE1rea at\xE9 o \xD7: s\xF3 se ele pedir.',
27119
27579
  '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.',
27120
27580
  '"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.'