beplus-mcp 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/index.js +235 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,8 @@ Reinicie o cliente. Rode a tool **`whoami`** para confirmar o vínculo.
|
|
|
84
84
|
| `generate_image` | Gera imagem (nano-banana / gpt-image-2). Bloqueante ~90s; retorna URL + imagem inline. Suporta refs, tamanho/qualidade, e extras do gpt-image-2 (background, moderation, output_format/compression). |
|
|
85
85
|
| `generate_video` | Gera vídeo (Seedance / Kling). Aguarda ~120s; senão devolve o id pra `check_generation`. Suporta first/last frame, refs de imagem/vídeo/áudio, prompt negativo, áudio gerado, mode e motion-control. |
|
|
86
86
|
| `upscale` | Melhora a resolução de uma imagem ou vídeo que já existe (Topaz). Detecta o tipo pelo arquivo. Imagem até 4x (~20s, a partir de 6💎); vídeo até 30s e saída até 1080p, custa por bloco de 10s e **descarta o áudio**. Mede o arquivo sozinho para o preço sair certo. ⚠️ Vídeo é restrito à equipe. |
|
|
87
|
-
| `generate_audio` | Sintetiza fala (Gemini TTS,
|
|
87
|
+
| `generate_audio` | Sintetiza fala (Gemini TTS, multi-locutor). Padrão `gemini-3.8-flash-lite-tts`; os modelos 3.8 aceitam vozes da biblioteca, tags vocais (`<laugh>`, `<sigh>`...) e `style` por locutor; os legados só as 30 vozes clássicas. Síncrono. |
|
|
88
|
+
| `list_tts_voices` | Lista as vozes da biblioteca do TTS 3.8 (filtros: idioma, gênero, tom, contexto, busca) com o id para usar em `voice`. Somente leitura. |
|
|
88
89
|
| `generate_music` | Gera música completa (Suno v5.5 / v4.5) — modo descrição ou letra própria, tags, instrumental, vocal_gender, controles criativos. Async. |
|
|
89
90
|
| `check_generation` | Status de uma geração async por id. |
|
|
90
91
|
| `cancel_generation` | Cancela uma geração em fila/processamento (estorna diamantes). |
|
|
@@ -140,10 +141,13 @@ Falam com `/api/v1/canvas`. Montam e editam o fluxo e **não geram mídia**, com
|
|
|
140
141
|
| `canvas_reopen_comment` | Reabre, com motivo opcional. |
|
|
141
142
|
| `canvas_comment` | Cria comentário raiz ancorado (nó, versão, minutagem, ponto ou board), para deixar pergunta na tela da pessoa. |
|
|
142
143
|
| `canvas_run` | Roda nós pela aba do Canvas aberta da própria pessoa (mesma conta; sem aba, nada roda). `max_diamonds` obrigatório: a aba estima como o botão Rodar e só roda se couber. `dry_run: true` só estima. Alvo: `node_ids` (roda de novo mesmo em cache, como o Rodar do cartão), `from_node_id` (daqui para frente) ou `all: true` (só o que mudou). `simulated: true` exige a aba no Simulado; sem ele, a aba precisa estar no Real. Prompt com PT sem aplicar no EN: recusa e diz qual. Devolve o que vai rodar, o custo e o `request_id`. |
|
|
143
|
-
| `canvas_run_status` | Andamento de um `request_id`: estado de cada nó, estimado e gasto, perguntado à aba que rodou. Com a aba fechada, lê o estado de execução do board para os nós do pedido. |
|
|
144
|
+
| `canvas_run_status` | Andamento de um `request_id`: estado de cada nó, estimado e gasto, perguntado à aba que rodou. Com a aba fechada, lê o estado de execução do board para os nós do pedido. Resposta grande vem cortada ("… e mais N"); o andamento sem aba de um pedido cortado avisa que é parcial. |
|
|
145
|
+
| `canvas_focus` | "Olha aqui": leva a aba do Canvas que a pessoa está olhando (a visível que não é aba de agente aberta por link `?frame`; sem nenhuma, a de um agente) até `node_ids` e/ou `section_id`, sem animação, e seleciona os nós (`select: false` não mexe). **Padrão: uma vez só**, a aba não fica presa. `follow: true` segue a área e prende a aba até o "×" da pílula (modo `?frame`, com o `titulo` na pílula e no nome da aba). `zoom`: `"fit"` (padrão) ou número de 0,07 a 4. **Não gasta e não escreve no board.** Devolve qual aba enquadrou (nome, do usuário ou de agente, repassada, escondida, Montagem aberta) e os ids que não existem. Qualquer papel, leitor inclusive; pede só `ialab:read`. |
|
|
144
146
|
|
|
145
147
|
Rodar pelo MCP (`canvas_run`): o servidor (academy-backend, rota `run-requests`) repassa o pedido para **uma** aba da própria pessoa (a aberta por último) e nunca roda nada. Sem resposta em 20 s, o destino fica incerto: a ferramenta manda consultar `canvas_run_status` com o mesmo `request_id`, e repetir `canvas_run` com esse `request_id` é seguro (a aba não roda duas vezes).
|
|
146
148
|
|
|
149
|
+
Enquadrar pelo MCP (`canvas_focus`): o servidor (rota `focus-requests`) repassa para **uma** aba da própria pessoa, a de entrada mais recente com a versão do Canvas que enquadra (a aba antiga não conta: recarregue). Essa aba passa o pedido à aba que a pessoa está olhando no mesmo navegador. Sem resposta em 5 s, 504; chamar de novo é seguro. Por padrão a aba só vai até a área uma vez; com `follow: true` ela segue a área até a pessoa clicar no "×".
|
|
150
|
+
|
|
147
151
|
Concorrência: cada edição relê o board, vai num envelope só (um `seq`) e é recusada, sem aplicar nada, se outra pessoa mexeu nos mesmos nós desde a sua leitura. Recusas do servidor (`node-missing` e afins) voltam em português com "releia". As exceções são o `canvas_patch_node` e o `canvas_update_nodes`: vários envelopes em ordem, cada um julgado sozinho, e o relatório diz qual entrou. A medida que a tela grava sozinha (`data.__measured`) não conta como "outra pessoa mexeu".
|
|
148
152
|
|
|
149
153
|
Tamanho do cartão: o `size` gravado é **piso**; o cartão cresce com o conteúdo (um Prompt de 340 de largura com o `!!ESTILO` resolvido vira uma coluna de 1.800). O Canvas grava o tamanho desenhado em `data.__measured = { w, h, at }` quando o cartão passa do piso; as leituras mostram esse (`medido`) ou o piso (`gravado`). É campo interno: não aparece como dado e nenhuma ferramenta grava nele. `size: {w, h}` muda a caixa; `size: "titulo" | "subtitulo" | "corpo"` é o tamanho do texto do Texto livre (`data.size` do nó `label`).
|
package/dist/index.js
CHANGED
|
@@ -21621,6 +21621,9 @@ var BeplusClient = class {
|
|
|
21621
21621
|
tts(body2) {
|
|
21622
21622
|
return this.request("POST", "/tts", { body: body2, timeoutMs: 9e4 });
|
|
21623
21623
|
}
|
|
21624
|
+
ttsVoices(query) {
|
|
21625
|
+
return this.request("GET", "/tts/voices", { query, timeoutMs: 2e4 });
|
|
21626
|
+
}
|
|
21624
21627
|
models() {
|
|
21625
21628
|
return this.request("GET", "/models");
|
|
21626
21629
|
}
|
|
@@ -22179,9 +22182,13 @@ var MUSIC_MODELS = [
|
|
|
22179
22182
|
];
|
|
22180
22183
|
var VOCAL_GENDERS = ["f", "m"];
|
|
22181
22184
|
var TTS_MODELS = [
|
|
22185
|
+
"gemini-3.8-flash-lite-tts",
|
|
22186
|
+
"gemini-3.8-flash-tts",
|
|
22182
22187
|
"gemini-3.1-flash-tts-preview",
|
|
22183
22188
|
"gemini-2.5-pro-preview-tts"
|
|
22184
22189
|
];
|
|
22190
|
+
var TTS_MODELS_38 = ["gemini-3.8-flash-lite-tts", "gemini-3.8-flash-tts"];
|
|
22191
|
+
var LIBRARY_VOICE_ID = /^[a-z]{2,3}-[a-z0-9]{2,4}-[a-z0-9-]{1,60}$/;
|
|
22185
22192
|
var VOICES = [
|
|
22186
22193
|
"Zephyr",
|
|
22187
22194
|
"Puck",
|
|
@@ -22226,7 +22233,7 @@ var DEFAULTS = {
|
|
|
22226
22233
|
videoAspect: "16:9",
|
|
22227
22234
|
videoResolution: "720p",
|
|
22228
22235
|
videoDuration: 5,
|
|
22229
|
-
ttsModel: "gemini-3.
|
|
22236
|
+
ttsModel: "gemini-3.8-flash-lite-tts",
|
|
22230
22237
|
voice: "Kore",
|
|
22231
22238
|
musicModel: "suno/v5-5"
|
|
22232
22239
|
};
|
|
@@ -23463,26 +23470,50 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
23463
23470
|
}
|
|
23464
23471
|
|
|
23465
23472
|
// src/tools/audio.ts
|
|
23473
|
+
var VOICE_HELP = "Uma das 30 vozes cl\xE1ssicas (Kore, Puck, Charon...) OU o id de uma voz da biblioteca (ex.: pt-br-advisor-10, s\xF3 nos modelos 3.8; descubra ids com list_tts_voices).";
|
|
23474
|
+
var voiceSchema = external_exports.string().refine((v) => VOICES.includes(v) || LIBRARY_VOICE_ID.test(v), {
|
|
23475
|
+
message: "Voz inv\xE1lida: use uma das 30 vozes cl\xE1ssicas (ex.: Kore) ou um id da biblioteca (ex.: pt-br-advisor-10)."
|
|
23476
|
+
});
|
|
23477
|
+
function isLibraryVoice(v) {
|
|
23478
|
+
return !VOICES.includes(v) && LIBRARY_VOICE_ID.test(v);
|
|
23479
|
+
}
|
|
23480
|
+
var speakerSchema = external_exports.object({
|
|
23481
|
+
name: external_exports.string().min(1).max(50),
|
|
23482
|
+
voice: voiceSchema.describe(VOICE_HELP),
|
|
23483
|
+
style: external_exports.string().max(500).optional().describe('Entrega deste locutor (s\xF3 modelos 3.8), ex.: "calmo, grave, pausado".')
|
|
23484
|
+
});
|
|
23466
23485
|
function registerAudioTools(server, client, cfg) {
|
|
23467
23486
|
server.registerTool(
|
|
23468
23487
|
"generate_audio",
|
|
23469
23488
|
{
|
|
23470
23489
|
title: "Gerar \xE1udio (TTS)",
|
|
23471
|
-
description: 'Sintetiza fala a partir de texto (s\xEDncrono \u2014 retorna a URL na hora) usando o IA Lab da BePlus.
|
|
23490
|
+
description: 'Sintetiza fala a partir de texto (s\xEDncrono \u2014 retorna a URL na hora) usando o IA Lab da BePlus. Modelos Gemini 3.8 (`gemini-3.8-flash-lite-tts`, padr\xE3o, r\xE1pido e barato; `gemini-3.8-flash-tts`, qualidade de est\xFAdio, melhor para di\xE1logo e narra\xE7\xE3o longa) aceitam: vozes da biblioteca (milhares, por idioma; liste com `list_tts_voices` e passe o id em `voice`), tags vocais no texto como <laugh> <chuckle> <sigh> <breath> <gasp> <cough> <sneeze> <throat-clearing> <yawn> <groan> <scream> <whimper> <argh> <short pause> <long pause> (viram som de verdade), e estilo por locutor (`style` em cada speaker). No 3.8, `style_instruction` \xE9 dire\xE7\xE3o e N\xC3O \xE9 lida em voz alta. Os modelos legados (`gemini-3.1-flash-tts-preview`, `gemini-2.5-pro-preview-tts`) s\xF3 aceitam as 30 vozes cl\xE1ssicas (Kore, Puck, Charon...) e n\xE3o t\xEAm voz da biblioteca. Opcional `style_instruction` (ex.: "fale animado e pausado") e di\xE1logo com 2 locutores via `multi_speaker`. Consome diamantes por segundo de \xE1udio.' + TEAM_GATING_NOTE,
|
|
23472
23491
|
inputSchema: {
|
|
23473
23492
|
text: external_exports.string().min(1).max(1e4).describe("Texto a ser falado."),
|
|
23474
|
-
voice:
|
|
23493
|
+
voice: voiceSchema.default(DEFAULTS.voice).describe(VOICE_HELP),
|
|
23475
23494
|
model: external_exports.enum(TTS_MODELS).default(DEFAULTS.ttsModel).describe("Modelo TTS."),
|
|
23476
23495
|
style_instruction: external_exports.string().max(2e3).optional().describe("Instru\xE7\xE3o de estilo/tom aplicada \xE0 fala."),
|
|
23477
23496
|
multi_speaker: external_exports.object({
|
|
23478
|
-
speaker1:
|
|
23479
|
-
speaker2:
|
|
23497
|
+
speaker1: speakerSchema,
|
|
23498
|
+
speaker2: speakerSchema
|
|
23480
23499
|
}).optional().describe('Di\xE1logo de 2 locutores. Marque as falas no texto como "Nome: ...".'),
|
|
23481
23500
|
project: external_exports.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). Omita para usar o projeto ativo da conta.')
|
|
23482
23501
|
}
|
|
23483
23502
|
},
|
|
23484
23503
|
async (args) => {
|
|
23485
23504
|
try {
|
|
23505
|
+
const ms = args.multi_speaker;
|
|
23506
|
+
const voices = [args.voice, ms?.speaker1.voice, ms?.speaker2.voice].filter((v) => !!v);
|
|
23507
|
+
if (!TTS_MODELS_38.includes(args.model)) {
|
|
23508
|
+
if (voices.some(isLibraryVoice)) {
|
|
23509
|
+
return errorResult(
|
|
23510
|
+
`Vozes da biblioteca s\xF3 funcionam nos modelos Gemini 3.8 (gemini-3.8-flash-lite-tts, gemini-3.8-flash-tts). O modelo ${args.model} s\xF3 aceita as 30 vozes cl\xE1ssicas (Kore, Puck, Charon...).`
|
|
23511
|
+
);
|
|
23512
|
+
}
|
|
23513
|
+
if (ms?.speaker1.style || ms?.speaker2.style) {
|
|
23514
|
+
return errorResult("O estilo por locutor (`style`) s\xF3 funciona nos modelos Gemini 3.8.");
|
|
23515
|
+
}
|
|
23516
|
+
}
|
|
23486
23517
|
const body2 = compact({
|
|
23487
23518
|
text: args.text,
|
|
23488
23519
|
voice: args.voice,
|
|
@@ -23516,6 +23547,45 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
|
|
|
23516
23547
|
}
|
|
23517
23548
|
}
|
|
23518
23549
|
);
|
|
23550
|
+
server.registerTool(
|
|
23551
|
+
"list_tts_voices",
|
|
23552
|
+
{
|
|
23553
|
+
title: "Listar vozes da biblioteca TTS",
|
|
23554
|
+
description: 'Lista vozes da biblioteca do Gemini 3.8 TTS (n\xE3o inclui as 30 cl\xE1ssicas como Kore e Puck). Filtre por idioma (`language`, BCP-47, padr\xE3o pt-BR; "all" = todos), `gender`, `pitch`, `context` (ex.: Audiobook) e `search` (texto livre em nome, persona, descri\xE7\xE3o, sotaque). Passe o `id` da voz em `voice` do generate_audio com um modelo 3.8 (gemini-3.8-flash-lite-tts ou gemini-3.8-flash-tts). Somente leitura, sem custo.',
|
|
23555
|
+
inputSchema: {
|
|
23556
|
+
language: external_exports.string().max(12).optional().describe('Idioma BCP-47, ex.: pt-BR, en-US. Padr\xE3o pt-BR. "all" = sem filtro.'),
|
|
23557
|
+
gender: external_exports.enum(["female", "male", "neutral"]).optional(),
|
|
23558
|
+
pitch: external_exports.enum(["low", "medium", "high"]).optional(),
|
|
23559
|
+
context: external_exports.string().max(60).optional().describe("Uso da voz (trecho), ex.: Audiobook, Enterprise Agent."),
|
|
23560
|
+
search: external_exports.string().max(100).optional().describe("Texto livre: nome, persona, descri\xE7\xE3o, sotaque."),
|
|
23561
|
+
limit: external_exports.number().int().min(1).max(200).optional().describe("Padr\xE3o 60."),
|
|
23562
|
+
offset: external_exports.number().int().min(0).optional()
|
|
23563
|
+
}
|
|
23564
|
+
},
|
|
23565
|
+
async (args) => {
|
|
23566
|
+
try {
|
|
23567
|
+
const res = await client.ttsVoices(compact(args));
|
|
23568
|
+
const offset = args.offset ?? 0;
|
|
23569
|
+
if (!res.voices.length) {
|
|
23570
|
+
return textResult(`Nenhuma voz encontrada (total ${res.total}). Idiomas dispon\xEDveis: ${res.languages.join(", ")}.`);
|
|
23571
|
+
}
|
|
23572
|
+
const lines = res.voices.map((v) => {
|
|
23573
|
+
const desc = v.description ? ` | ${v.description.length > 110 ? `${v.description.slice(0, 107)}...` : v.description}` : "";
|
|
23574
|
+
return `${v.id} | ${v.name} | ${v.gender} | ${v.pitch}${desc}`;
|
|
23575
|
+
});
|
|
23576
|
+
const more = res.total > offset + res.voices.length ? ` Use offset=${offset + res.voices.length} para ver mais.` : "";
|
|
23577
|
+
return textResult(
|
|
23578
|
+
`Vozes ${offset + 1}-${offset + res.voices.length} de ${res.total} (id | nome | g\xEAnero | tom | descri\xE7\xE3o):
|
|
23579
|
+
${lines.join("\n")}
|
|
23580
|
+
|
|
23581
|
+
Use o id em voice (modelos 3.8).${more}
|
|
23582
|
+
Contextos: ${res.contexts.join(", ")}.`
|
|
23583
|
+
);
|
|
23584
|
+
} catch (e) {
|
|
23585
|
+
return errorResult(apiErrorToUserMessage(e));
|
|
23586
|
+
}
|
|
23587
|
+
}
|
|
23588
|
+
);
|
|
23519
23589
|
}
|
|
23520
23590
|
|
|
23521
23591
|
// src/tools/music.ts
|
|
@@ -24087,6 +24157,7 @@ import { createHash } from "crypto";
|
|
|
24087
24157
|
// src/canvas/api.ts
|
|
24088
24158
|
var PINNED_CORE_VERSION = "ca7b3b406184c7f679f555c3a008013197182b23";
|
|
24089
24159
|
var RUN_REQUEST_TIMEOUT_MS = 4e4;
|
|
24160
|
+
var FOCUS_REQUEST_TIMEOUT_MS = 12e3;
|
|
24090
24161
|
function errorRaw(err) {
|
|
24091
24162
|
if (!(err instanceof ApiError)) return void 0;
|
|
24092
24163
|
const raw = err.body?.error?.raw;
|
|
@@ -24221,6 +24292,11 @@ var CanvasApi = class {
|
|
|
24221
24292
|
runStatus(boardId4, requestId) {
|
|
24222
24293
|
return this.req("GET", `/boards/${boardId4}/run-requests/${requestId}`);
|
|
24223
24294
|
}
|
|
24295
|
+
// ── enquadrar a aba do Canvas (CF1) ───────────────────────────────────────
|
|
24296
|
+
/** Pede para a aba do Canvas da pessoa enquadrar nós. O servidor espera a aba até 5 s. */
|
|
24297
|
+
focusRequest(boardId4, body2) {
|
|
24298
|
+
return this.req("POST", `/boards/${boardId4}/focus-requests`, { body: body2, timeoutMs: FOCUS_REQUEST_TIMEOUT_MS });
|
|
24299
|
+
}
|
|
24224
24300
|
/** Só para os testes conferirem o aprendizado. */
|
|
24225
24301
|
get currentCoreVersion() {
|
|
24226
24302
|
return this.coreVersion;
|
|
@@ -24315,7 +24391,7 @@ var KINDS = {
|
|
|
24315
24391
|
tts: {
|
|
24316
24392
|
title: "Locu\xE7\xE3o",
|
|
24317
24393
|
size: { w: 320, h: 212 },
|
|
24318
|
-
data: { model: "gemini-3.
|
|
24394
|
+
data: { model: "gemini-3.8-flash-lite-tts", voice: "Algieba", speed: "normal" },
|
|
24319
24395
|
inputs: ["text"],
|
|
24320
24396
|
outputs: ["audio", "approved"],
|
|
24321
24397
|
dynamic: "style conforme o modelo"
|
|
@@ -24551,13 +24627,13 @@ function makeEdge(init) {
|
|
|
24551
24627
|
}
|
|
24552
24628
|
var setValue = (value) => ({ kind: "value", value });
|
|
24553
24629
|
var unsetValue = () => ({ kind: "unset" });
|
|
24554
|
-
function removeNodeOps(doc,
|
|
24630
|
+
function removeNodeOps(doc, nodeId3) {
|
|
24555
24631
|
const ops = [];
|
|
24556
|
-
const edgeIds = Object.keys(doc.edges).filter((id) => doc.edges[id].source ===
|
|
24632
|
+
const edgeIds = Object.keys(doc.edges).filter((id) => doc.edges[id].source === nodeId3 || doc.edges[id].target === nodeId3).sort();
|
|
24557
24633
|
for (const edgeId of edgeIds) ops.push({ type: "edge.remove", edgeId });
|
|
24558
|
-
const history = Object.hasOwn(doc.nodeHistory,
|
|
24559
|
-
if (history) for (const resultId of [...history].reverse()) ops.push({ type: "history.remove", nodeId:
|
|
24560
|
-
ops.push({ type: "node.remove", nodeId:
|
|
24634
|
+
const history = Object.hasOwn(doc.nodeHistory, nodeId3) ? doc.nodeHistory[nodeId3] : void 0;
|
|
24635
|
+
if (history) for (const resultId of [...history].reverse()) ops.push({ type: "history.remove", nodeId: nodeId3, resultId });
|
|
24636
|
+
ops.push({ type: "node.remove", nodeId: nodeId3 });
|
|
24561
24637
|
return ops;
|
|
24562
24638
|
}
|
|
24563
24639
|
function batchOf(ops) {
|
|
@@ -24958,8 +25034,8 @@ function mmss(t) {
|
|
|
24958
25034
|
const pad = (n) => String(n).padStart(2, "0");
|
|
24959
25035
|
return h ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
24960
25036
|
}
|
|
24961
|
-
function nodeRef(doc,
|
|
24962
|
-
const node = doc.nodes[
|
|
25037
|
+
function nodeRef(doc, nodeId3) {
|
|
25038
|
+
const node = doc.nodes[nodeId3];
|
|
24963
25039
|
const kind = nodeKind(node) ?? node.type;
|
|
24964
25040
|
return `"${oneLine(nodeName(node), 60)}" (${kind}, ${node.id})`;
|
|
24965
25041
|
}
|
|
@@ -24983,13 +25059,13 @@ function outputKeyOf(entry) {
|
|
|
24983
25059
|
if (typeof entry.url === "string" && entry.url !== "") return `url-${hashString(entry.url)}`;
|
|
24984
25060
|
return null;
|
|
24985
25061
|
}
|
|
24986
|
-
function versionOf(doc,
|
|
24987
|
-
const ids = doc.nodeHistory?.[
|
|
25062
|
+
function versionOf(doc, nodeId3, outputKey) {
|
|
25063
|
+
const ids = doc.nodeHistory?.[nodeId3];
|
|
24988
25064
|
if (Array.isArray(ids)) {
|
|
24989
25065
|
const i = ids.indexOf(outputKey);
|
|
24990
25066
|
if (i >= 0) return i + 1;
|
|
24991
25067
|
}
|
|
24992
|
-
const history = doc.nodes[
|
|
25068
|
+
const history = doc.nodes[nodeId3]?.data.history;
|
|
24993
25069
|
if (Array.isArray(history)) {
|
|
24994
25070
|
const i = history.findIndex(
|
|
24995
25071
|
(h) => typeof h === "object" && h !== null && !Array.isArray(h) && outputKeyOf(h) === outputKey
|
|
@@ -25459,17 +25535,17 @@ function holdsOne(doc, node) {
|
|
|
25459
25535
|
if (node.data.holds === "many") return false;
|
|
25460
25536
|
return itemsOf(node).length === 1 && Object.values(doc.edges).some((e) => e.source === node.id);
|
|
25461
25537
|
}
|
|
25462
|
-
function needMediaNode(doc,
|
|
25463
|
-
const node = doc.nodes[
|
|
25464
|
-
if (!node) throw new CanvasToolError(`N\xF3 ${
|
|
25538
|
+
function needMediaNode(doc, nodeId3) {
|
|
25539
|
+
const node = doc.nodes[nodeId3];
|
|
25540
|
+
if (!node) throw new CanvasToolError(`N\xF3 ${nodeId3} n\xE3o existe no board. Releia com canvas_read_board.`);
|
|
25465
25541
|
if (nodeKind(node) !== "media") {
|
|
25466
25542
|
throw new CanvasToolError(
|
|
25467
|
-
`O n\xF3 ${
|
|
25543
|
+
`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.`
|
|
25468
25544
|
);
|
|
25469
25545
|
}
|
|
25470
25546
|
return node;
|
|
25471
25547
|
}
|
|
25472
|
-
function payload(u,
|
|
25548
|
+
function payload(u, nodeId3) {
|
|
25473
25549
|
const out = { url: u.url, mime: u.mime, bytes: u.bytes };
|
|
25474
25550
|
if (u.meta.duration !== void 0) out.duration = u.meta.duration;
|
|
25475
25551
|
if (u.meta.width !== void 0) out.width = u.meta.width;
|
|
@@ -25478,14 +25554,14 @@ function payload(u, nodeId2) {
|
|
|
25478
25554
|
out.media_item = mediaItem(u);
|
|
25479
25555
|
if (u.kind !== "file") {
|
|
25480
25556
|
out.sequence_asset = sequenceAsset(u);
|
|
25481
|
-
out.libraryItemId = libraryItemId(u,
|
|
25557
|
+
out.libraryItemId = libraryItemId(u, nodeId3);
|
|
25482
25558
|
}
|
|
25483
25559
|
return JSON.stringify(out);
|
|
25484
25560
|
}
|
|
25485
|
-
function sequenceHint(u,
|
|
25561
|
+
function sequenceHint(u, nodeId3) {
|
|
25486
25562
|
if (u.kind === "file") return "Documento: entra no n\xF3 M\xEDdia, n\xE3o na timeline da Montagem.";
|
|
25487
25563
|
const lines = [
|
|
25488
|
-
"Num clipe da timeline da Montagem (data.timeline; data.sequence s\xF3 em n\xF3 que ainda n\xE3o migrou): use sequence_asset como `asset` e libraryItemId como est\xE1" + (
|
|
25564
|
+
"Num clipe da timeline da Montagem (data.timeline; data.sequence s\xF3 em n\xF3 que ainda n\xE3o migrou): use sequence_asset como `asset` e libraryItemId como est\xE1" + (nodeId3 ? "." : ", trocando <id do n\xF3 M\xEDdia> pelo id do n\xF3 onde o item ficou.")
|
|
25489
25565
|
];
|
|
25490
25566
|
if (u.meta.duration !== void 0) {
|
|
25491
25567
|
const at30 = Math.floor(u.meta.duration * 30 + 1e-9);
|
|
@@ -25543,13 +25619,13 @@ URL: ${u.url}`;
|
|
|
25543
25619
|
return [head, "", payload(u), "", sequenceHint(u)].join("\n");
|
|
25544
25620
|
}
|
|
25545
25621
|
const boardId4 = args.board_id;
|
|
25546
|
-
let
|
|
25622
|
+
let nodeId3 = args.node_id;
|
|
25547
25623
|
let done;
|
|
25548
25624
|
try {
|
|
25549
25625
|
done = await edit(boardId4, async (doc) => {
|
|
25550
25626
|
const item = mediaItem(u);
|
|
25551
|
-
if (
|
|
25552
|
-
const node = needMediaNode(doc,
|
|
25627
|
+
if (nodeId3) {
|
|
25628
|
+
const node = needMediaNode(doc, nodeId3);
|
|
25553
25629
|
const replacing = args.replace ?? holdsOne(doc, node);
|
|
25554
25630
|
const items = replacing ? [item] : [...itemsOf(node), item];
|
|
25555
25631
|
if (items.length > MAX_MEDIA_ITEMS) {
|
|
@@ -25571,7 +25647,7 @@ URL: ${u.url}`;
|
|
|
25571
25647
|
zIndex: Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1,
|
|
25572
25648
|
createdBy: await api.me()
|
|
25573
25649
|
});
|
|
25574
|
-
|
|
25650
|
+
nodeId3 = created.id;
|
|
25575
25651
|
return {
|
|
25576
25652
|
ops: [{ type: "node.add", nodeId: created.id, node: created }],
|
|
25577
25653
|
targets: [],
|
|
@@ -25587,7 +25663,7 @@ N\xE3o suba de novo: releia com canvas_read_board e grave media_item em data.ite
|
|
|
25587
25663
|
${payload(u)}`
|
|
25588
25664
|
);
|
|
25589
25665
|
}
|
|
25590
|
-
return [head, done, "", payload(u,
|
|
25666
|
+
return [head, done, "", payload(u, nodeId3), "", sequenceHint(u, nodeId3)].join("\n");
|
|
25591
25667
|
})
|
|
25592
25668
|
);
|
|
25593
25669
|
}
|
|
@@ -25615,12 +25691,16 @@ function estimateLines(estimate, titleOf) {
|
|
|
25615
25691
|
const cost = n.error ? `estimativa falhou (${n.error})` : dia(n.diamonds);
|
|
25616
25692
|
lines.push(`- ${n.title ?? titleOf(n.nodeId)} (${n.nodeId}): ${cost}`);
|
|
25617
25693
|
}
|
|
25618
|
-
|
|
25694
|
+
if (estimate.omitted) {
|
|
25695
|
+
lines.push(`\u2026 e mais ${estimate.omitted} n\xF3(s) fora da lista (a resposta da aba foi cortada pelo tamanho; o total vale para todos).`);
|
|
25696
|
+
}
|
|
25697
|
+
const cached2 = estimate.cached ?? nodes.filter((n) => !n.willRun && !n.afterApproval).length;
|
|
25619
25698
|
if (cached2) lines.push(`Em cache (n\xE3o rodam): ${cached2}.`);
|
|
25620
|
-
const later = estimate.later?.nodes?.length ?? 0;
|
|
25699
|
+
const later = estimate.counts?.later ?? estimate.later?.nodes?.length ?? 0;
|
|
25621
25700
|
if (later) lines.push(`Esperando aprova\xE7\xE3o no board depois disso: ${later} n\xF3(s), \u2248 ${dia(estimate.later?.total ?? 0)} (fora desta conta).`);
|
|
25622
|
-
|
|
25623
|
-
|
|
25701
|
+
const unknown2 = estimate.counts?.unknown ?? estimate.unknown?.length ?? 0;
|
|
25702
|
+
if (unknown2) {
|
|
25703
|
+
lines.push(`Sem estimativa: ${unknown2} n\xF3(s) (texto cobra por token; o total \xE9 piso).`);
|
|
25624
25704
|
}
|
|
25625
25705
|
return lines;
|
|
25626
25706
|
}
|
|
@@ -25648,7 +25728,7 @@ function registerCanvasRunTools(server, api, run) {
|
|
|
25648
25728
|
return (id) => id;
|
|
25649
25729
|
}
|
|
25650
25730
|
}
|
|
25651
|
-
function
|
|
25731
|
+
function explain2(e, requestId) {
|
|
25652
25732
|
if (!(e instanceof ApiError)) throw e;
|
|
25653
25733
|
const reason = errorRaw(e)?.reason;
|
|
25654
25734
|
const unknownFate = requestId && (e.httpStatus === 0 || e.httpStatus === 408 || reason === "tab-timeout" || reason === "tab-bad-answer" || e.httpStatus >= 500 && e.httpStatus !== 501);
|
|
@@ -25730,7 +25810,7 @@ function registerCanvasRunTools(server, api, run) {
|
|
|
25730
25810
|
label
|
|
25731
25811
|
});
|
|
25732
25812
|
} catch (e) {
|
|
25733
|
-
|
|
25813
|
+
explain2(e, requestId);
|
|
25734
25814
|
}
|
|
25735
25815
|
const titleOf = await titles(args.board_id);
|
|
25736
25816
|
if (answer.ok !== true) {
|
|
@@ -25743,7 +25823,8 @@ function registerCanvasRunTools(server, api, run) {
|
|
|
25743
25823
|
if (answer.status === "started") {
|
|
25744
25824
|
remember(requestId, {
|
|
25745
25825
|
boardId: args.board_id,
|
|
25746
|
-
nodeIds: nodeIds.length ? nodeIds : target.kind === "nodes" ? target.nodeIds : target.kind === "from" ? [target.nodeId] : []
|
|
25826
|
+
nodeIds: nodeIds.length ? nodeIds : target.kind === "nodes" ? target.nodeIds : target.kind === "from" ? [target.nodeId] : [],
|
|
25827
|
+
...answer.estimate?.omitted ? { omitted: answer.estimate.omitted } : {}
|
|
25747
25828
|
});
|
|
25748
25829
|
}
|
|
25749
25830
|
return answerText(answer, requestId, args.max_diamonds, titleOf);
|
|
@@ -25768,14 +25849,16 @@ function registerCanvasRunTools(server, api, run) {
|
|
|
25768
25849
|
return statusText(await api.runStatus(board_id, requestId), await titles(board_id));
|
|
25769
25850
|
} catch (e) {
|
|
25770
25851
|
const reason = errorRaw(e)?.reason;
|
|
25771
|
-
if (!(e instanceof ApiError) || reason !== "unknown-request" && reason !== "no-tab")
|
|
25852
|
+
if (!(e instanceof ApiError) || reason !== "unknown-request" && reason !== "no-tab") explain2(e);
|
|
25772
25853
|
why = reason === "no-tab" ? "Nenhuma aba do Canvas sua est\xE1 aberta neste board." : "Nenhuma aba aberta conhece este pedido.";
|
|
25773
25854
|
}
|
|
25774
|
-
const
|
|
25855
|
+
const known = node_ids ? void 0 : remembered.get(requestId);
|
|
25856
|
+
const ids = node_ids ?? known?.nodeIds;
|
|
25775
25857
|
if (!ids?.length) {
|
|
25776
25858
|
return `${why} Sem a aba, o andamento sai do estado de execu\xE7\xE3o do board: chame de novo com node_ids (os n\xF3s que o pedido rodava).`;
|
|
25777
25859
|
}
|
|
25778
|
-
|
|
25860
|
+
const partial2 = known?.omitted ? ` (parcial: s\xF3 os ${ids.length} n\xF3s que a aba listou; outros ${known.omitted} ficaram fora da resposta. Para eles, chame com node_ids)` : "";
|
|
25861
|
+
return `${why} Pelo estado de execu\xE7\xE3o do board${partial2}:
|
|
25779
25862
|
${fromRuns(await api.snapshot(board_id), ids)}`;
|
|
25780
25863
|
})
|
|
25781
25864
|
);
|
|
@@ -25815,6 +25898,7 @@ function statusText(a, titleOf) {
|
|
|
25815
25898
|
const outs = typeof n.outputs === "number" && n.outputs > 0 ? ` (${n.outputs} sa\xEDda(s))` : "";
|
|
25816
25899
|
lines.push(`- ${n.title ?? titleOf(n.nodeId)} (${n.nodeId}): ${NODE_TEXT[n.status] ?? n.status}${outs}${extra}`);
|
|
25817
25900
|
}
|
|
25901
|
+
if (a.omitted) lines.push(`\u2026 e mais ${a.omitted} n\xF3(s) fora da lista (a resposta da aba foi cortada pelo tamanho).`);
|
|
25818
25902
|
if (a.state === "done" || a.state === "failed") lines.push("Leia o resultado com canvas_read_board (node_ids).");
|
|
25819
25903
|
return lines.join("\n");
|
|
25820
25904
|
}
|
|
@@ -25834,6 +25918,112 @@ function fromRuns(snap, ids) {
|
|
|
25834
25918
|
}).join("\n");
|
|
25835
25919
|
}
|
|
25836
25920
|
|
|
25921
|
+
// src/tools/canvas-focus.ts
|
|
25922
|
+
var MAX_IDS = 100;
|
|
25923
|
+
var nodeId2 = external_exports.string().min(1).max(128);
|
|
25924
|
+
var fmtZoom = (z) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 2 }).format(z);
|
|
25925
|
+
function explain(e, usesPat) {
|
|
25926
|
+
if (!(e instanceof ApiError)) throw e;
|
|
25927
|
+
const reason = errorRaw(e)?.reason;
|
|
25928
|
+
if (reason === "no-tab") {
|
|
25929
|
+
throw new CanvasToolError(
|
|
25930
|
+
"Nenhuma aba do Canvas sua est\xE1 aberta neste board (ou a aba \xE9 de uma vers\xE3o sem esta fun\xE7\xE3o). Pe\xE7a para abrir o board no Canvas com a mesma conta deste MCP (ou recarregar a aba) e tente de novo. Nada mudou."
|
|
25931
|
+
);
|
|
25932
|
+
}
|
|
25933
|
+
if (reason === "tab-timeout") {
|
|
25934
|
+
throw new CanvasToolError("A aba do Canvas n\xE3o respondeu em 5 s. Ela pode ter enquadrado; chamar de novo n\xE3o faz mal.");
|
|
25935
|
+
}
|
|
25936
|
+
if (reason === "tab-bad-answer") {
|
|
25937
|
+
throw new CanvasToolError("A aba do Canvas respondeu fora do contrato. Pe\xE7a para recarregar a aba e tente de novo.");
|
|
25938
|
+
}
|
|
25939
|
+
if (e.httpStatus === 404) {
|
|
25940
|
+
if (!/board/i.test(e.message)) {
|
|
25941
|
+
throw new CanvasToolError("O servidor do Canvas ainda n\xE3o tem o enquadramento pelo MCP (academy-backend sem a CF1). Nada mudou.");
|
|
25942
|
+
}
|
|
25943
|
+
throw new CanvasToolError("Board n\xE3o encontrado, ou voc\xEA n\xE3o \xE9 membro dele. Confira o id em canvas_list_boards.");
|
|
25944
|
+
}
|
|
25945
|
+
if (e.httpStatus === 403 && (!usesPat || /ialab:/.test(e.message))) {
|
|
25946
|
+
throw new CanvasToolError("O login pelo navegador deste MCP n\xE3o tem a permiss\xE3o que o enquadramento exige. Rode `beplus-mcp login` de novo e autorize.");
|
|
25947
|
+
}
|
|
25948
|
+
if (e.httpStatus === 400) throw new CanvasToolError(`O servidor recusou o pedido: ${e.message}.`);
|
|
25949
|
+
if (e.httpStatus === 429) throw new CanvasToolError("Limite de pedidos de enquadramento (60 por minuto). Espere um minuto.");
|
|
25950
|
+
throw e;
|
|
25951
|
+
}
|
|
25952
|
+
function focusText(a) {
|
|
25953
|
+
const tab = a.tab;
|
|
25954
|
+
const name = tab?.title ? `"${tab.title}"` : "aberta";
|
|
25955
|
+
const found = a.found ?? 0;
|
|
25956
|
+
const selected = a.selected ?? 0;
|
|
25957
|
+
const zoom = typeof a.zoom === "number" ? ` Zoom ${fmtZoom(a.zoom)}.` : "";
|
|
25958
|
+
const lines = [
|
|
25959
|
+
`Enquadrado na aba do Canvas ${name}: ${found} n\xF3(s)${selected > 0 ? `, ${selected} selecionado(s)` : ", sem mexer na sele\xE7\xE3o"}.${zoom}`
|
|
25960
|
+
];
|
|
25961
|
+
if (tab?.human !== void 0 || tab?.relayed !== void 0) {
|
|
25962
|
+
const who2 = tab.human === false ? "Aba de agente (link ?frame): nenhuma aba do usu\xE1rio estava vis\xEDvel neste board." : "Aba do usu\xE1rio (a que ele est\xE1 olhando).";
|
|
25963
|
+
const via = tab.relayed ? " Repassado pela aba que o servidor escolheu." : "";
|
|
25964
|
+
lines.push(`${who2}${via}`);
|
|
25965
|
+
}
|
|
25966
|
+
if (a.mode === "follow") {
|
|
25967
|
+
lines.push("Modo seguir: a aba acompanha essa \xE1rea e fica presa nela at\xE9 o usu\xE1rio clicar no \xD7 da p\xEDlula.");
|
|
25968
|
+
} else if (a.mode === "light") {
|
|
25969
|
+
lines.push("Uma vez s\xF3: a aba n\xE3o fica presa; o usu\xE1rio segue mexendo \xE0 vontade.");
|
|
25970
|
+
}
|
|
25971
|
+
const missing = a.missing ?? [];
|
|
25972
|
+
if (missing.length > 0) {
|
|
25973
|
+
const more = a.missingMore ? ` e mais ${a.missingMore}` : "";
|
|
25974
|
+
lines.push(`N\xE3o existem no board (ou est\xE3o arquivados): ${missing.join(", ")}${more}.`);
|
|
25975
|
+
}
|
|
25976
|
+
if (tab?.visible === false) lines.push("A aba est\xE1 escondida (atr\xE1s de outra): pe\xE7a para o usu\xE1rio traz\xEA-la para a frente.");
|
|
25977
|
+
if (tab?.montage) lines.push("O usu\xE1rio est\xE1 no editor (Montagem aberta nessa aba): o board foi enquadrado por tr\xE1s e aparece quando ele fechar a Montagem.");
|
|
25978
|
+
if (tab?.wasFramed) lines.push("Essa aba estava enquadrada em outra \xE1rea (talvez a de outro agente) e agora mostra esta.");
|
|
25979
|
+
return lines.join("\n");
|
|
25980
|
+
}
|
|
25981
|
+
function registerCanvasFocusTools(server, api, run) {
|
|
25982
|
+
server.registerTool(
|
|
25983
|
+
"canvas_focus",
|
|
25984
|
+
{
|
|
25985
|
+
title: "Canvas: enquadrar a aba do usu\xE1rio",
|
|
25986
|
+
description: 'Mostra "olha aqui" ao usu\xE1rio: leva a aba do Canvas que ele est\xE1 olhando neste board (a vis\xEDvel, que n\xE3o \xE9 aba de agente aberta por link ?frame; sem nenhuma, a de um agente) at\xE9 n\xF3s ou uma se\xE7\xE3o, sem anima\xE7\xE3o, e seleciona os n\xF3s (select: false n\xE3o mexe na sele\xE7\xE3o). Padr\xE3o: enquadra e seleciona UMA vez; a aba n\xE3o fica presa e o usu\xE1rio segue mexendo. follow: true segue a \xE1rea e prende a aba at\xE9 o \xD7 (modo ?frame: acompanha as mudan\xE7as, titulo na p\xEDlula e no nome da aba); use s\xF3 quando ele pedir para acompanhar. N\xC3O gasta e N\xC3O escreve no board. Diga node_ids e/ou section_id (at\xE9 100 ids; com os dois, a sele\xE7\xE3o fica nos node_ids). zoom: "fit" (padr\xE3o, cabe na tela) ou n\xFAmero de 0.07 a 4. Devolve qual aba enquadrou (nome, se \xE9 a do usu\xE1rio ou de agente, se veio repassada, se est\xE1 escondida, se ele est\xE1 no editor da Montagem) e os ids que n\xE3o existem. Sem aba: pe\xE7a para abrir (ou recarregar) o board no Canvas. Chamar de novo com a mesma \xE1rea \xE9 seguro.',
|
|
25987
|
+
inputSchema: {
|
|
25988
|
+
board_id: external_exports.string().uuid().describe("Id (uuid) do board."),
|
|
25989
|
+
node_ids: external_exports.array(nodeId2).min(1).max(MAX_IDS).optional().describe("Os n\xF3s a mostrar (e selecionar)."),
|
|
25990
|
+
section_id: nodeId2.optional().describe("Uma se\xE7\xE3o: enquadra a se\xE7\xE3o e o que est\xE1 dentro dela."),
|
|
25991
|
+
titulo: external_exports.string().trim().min(1).max(80).optional().describe('Com follow: texto da p\xEDlula e do nome da aba ("<titulo> \xB7 <board>"). Sem follow n\xE3o aparece.'),
|
|
25992
|
+
zoom: external_exports.union([external_exports.literal("fit"), external_exports.number().finite().min(0.07).max(4)]).optional().describe('"fit" (padr\xE3o): a \xE1rea cabe na tela, zoom at\xE9 100%. N\xFAmero: zoom fixo, com a \xE1rea no meio.'),
|
|
25993
|
+
select: external_exports.boolean().optional().describe("false: n\xE3o mexe na sele\xE7\xE3o. Padr\xE3o: seleciona os n\xF3s."),
|
|
25994
|
+
follow: external_exports.boolean().optional().describe("true: segue a \xE1rea e prende a aba at\xE9 o \xD7 da p\xEDlula. Padr\xE3o false: enquadra e seleciona uma vez, sem prender.")
|
|
25995
|
+
},
|
|
25996
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
25997
|
+
},
|
|
25998
|
+
async (args) => run(async () => {
|
|
25999
|
+
const nodeIds = args.node_ids ? [...new Set(args.node_ids)] : [];
|
|
26000
|
+
if (nodeIds.length === 0 && !args.section_id) {
|
|
26001
|
+
throw new CanvasToolError("Diga o que mostrar: node_ids (os n\xF3s), section_id (uma se\xE7\xE3o) ou os dois.");
|
|
26002
|
+
}
|
|
26003
|
+
if (nodeIds.length + (args.section_id ? 1 : 0) > MAX_IDS) {
|
|
26004
|
+
throw new CanvasToolError(`At\xE9 ${MAX_IDS} ids por pedido (node_ids mais a se\xE7\xE3o).`);
|
|
26005
|
+
}
|
|
26006
|
+
let answer;
|
|
26007
|
+
try {
|
|
26008
|
+
answer = await api.focusRequest(args.board_id, {
|
|
26009
|
+
...nodeIds.length ? { nodeIds } : {},
|
|
26010
|
+
...args.section_id ? { sectionId: args.section_id } : {},
|
|
26011
|
+
...args.titulo ? { title: args.titulo } : {},
|
|
26012
|
+
...args.zoom !== void 0 ? { zoom: args.zoom } : {},
|
|
26013
|
+
...args.select !== void 0 ? { select: args.select } : {},
|
|
26014
|
+
...args.follow !== void 0 ? { follow: args.follow } : {}
|
|
26015
|
+
});
|
|
26016
|
+
} catch (e) {
|
|
26017
|
+
explain(e, api.usesPat);
|
|
26018
|
+
}
|
|
26019
|
+
if (answer.ok !== true) {
|
|
26020
|
+
throw new CanvasToolError(`A aba recusou, nada mudou (${answer.reason ?? "sem motivo"}): ${answer.message ?? ""}`.trim());
|
|
26021
|
+
}
|
|
26022
|
+
return focusText(answer);
|
|
26023
|
+
})
|
|
26024
|
+
);
|
|
26025
|
+
}
|
|
26026
|
+
|
|
25837
26027
|
// src/canvas/mentions.ts
|
|
25838
26028
|
var VIA_MENTION = "viaMention";
|
|
25839
26029
|
var PROMPT_REFS_INPUT = "refs";
|
|
@@ -26124,9 +26314,9 @@ ${lines.join("\n")}
|
|
|
26124
26314
|
return `\u2705 ${done} em ${envs.length} envelope(s) (seq at\xE9 ${last}).
|
|
26125
26315
|
${lines.join("\n")}${note}`;
|
|
26126
26316
|
}
|
|
26127
|
-
function needNode(doc,
|
|
26128
|
-
const node = doc.nodes[
|
|
26129
|
-
if (!node) throw new CanvasToolError(`N\xF3 ${
|
|
26317
|
+
function needNode(doc, nodeId3) {
|
|
26318
|
+
const node = doc.nodes[nodeId3];
|
|
26319
|
+
if (!node) throw new CanvasToolError(`N\xF3 ${nodeId3} n\xE3o existe no board. Releia com canvas_read_board.`);
|
|
26130
26320
|
return node;
|
|
26131
26321
|
}
|
|
26132
26322
|
function portWarning(doc, source, sourcePort, target, targetPort) {
|
|
@@ -26740,13 +26930,14 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
26740
26930
|
registerCanvasMediaTools(server, { api, cfg, run, edit, seen, placeFor });
|
|
26741
26931
|
registerCanvasLayoutTools(server, { api, run, edit });
|
|
26742
26932
|
registerCanvasRunTools(server, api, run);
|
|
26933
|
+
registerCanvasFocusTools(server, api, run);
|
|
26743
26934
|
}
|
|
26744
26935
|
|
|
26745
26936
|
// src/version.ts
|
|
26746
26937
|
import { createRequire } from "module";
|
|
26747
26938
|
var require2 = createRequire(import.meta.url);
|
|
26748
26939
|
function readVersion() {
|
|
26749
|
-
if ("0.
|
|
26940
|
+
if ("0.33.0") return "0.33.0";
|
|
26750
26941
|
try {
|
|
26751
26942
|
const pkg = require2("../package.json");
|
|
26752
26943
|
return pkg.version ?? "0.0.0-unknown";
|
|
@@ -26762,6 +26953,7 @@ var SERVER_INSTRUCTIONS = [
|
|
|
26762
26953
|
'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.',
|
|
26763
26954
|
"Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
|
|
26764
26955
|
"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.",
|
|
26956
|
+
'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.',
|
|
26765
26957
|
'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.',
|
|
26766
26958
|
'"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.'
|
|
26767
26959
|
].join("\n");
|