diff --git a/client/src/App.tsx b/client/src/App.tsx
index 21ce75d..da45d01 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -147,6 +147,7 @@ function Router() {
+
diff --git a/client/src/pages/Skills.tsx b/client/src/pages/Skills.tsx
index c187d17..83a8424 100644
--- a/client/src/pages/Skills.tsx
+++ b/client/src/pages/Skills.tsx
@@ -1,5 +1,5 @@
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
-import { useState } from "react";
+import { useState, useRef, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -133,6 +133,10 @@ export default function Skills() {
const [historyOpen, setHistoryOpen] = useState(false);
const [historySkill, setHistorySkill] = useState(null);
+ // Ref para skills — usado no completion provider sem closure stale
+ const skillsRef = useRef([]);
+ const completionDisposable = useRef<{ dispose(): void } | null>(null);
+
// ── Queries ────────────────────────────────────────────────────────────────
const { data: skillsData, isLoading } = useQuery<{ skills: Skill[] }>({
@@ -263,6 +267,87 @@ export default function Skills() {
const skills = skillsData?.skills ?? [];
+ // Mantém ref atualizado para o completion provider
+ useEffect(() => { skillsRef.current = skills; }, [skills]);
+
+ // Cleanup do completion provider ao desmontar
+ useEffect(() => () => { completionDisposable.current?.dispose(); }, []);
+
+ // Handler do Monaco Body — registra completion provider de referências /
+ function handleBodyEditorMount(editor: unknown, monaco: any) {
+ completionDisposable.current?.dispose();
+
+ const PREFIXES = [
+ { label: "/skill/", detail: "Skill do tenant atual" },
+ { label: "/skill:system/", detail: "Skill global do sistema" },
+ { label: "/skill:company/", detail: "Skill da empresa" },
+ { label: "/var/", detail: "Variável de contexto ou parâmetro de entrada" },
+ { label: "/data/", detail: "Dado dinâmico do banco" },
+ { label: "/kg/", detail: "Nó do Knowledge Graph (Neo4j)" },
+ { label: "/file/", detail: "Arquivo no storage" },
+ { label: "/tool/", detail: "Tool disponível no Manus" },
+ { label: "/agent/", detail: "Agente especialista" },
+ ];
+
+ completionDisposable.current = monaco.languages.registerCompletionItemProvider("markdown", {
+ triggerCharacters: ["/"],
+ provideCompletionItems(model: any, position: any) {
+ const line = model.getValueInRange({
+ startLineNumber: position.lineNumber,
+ startColumn: 1,
+ endLineNumber: position.lineNumber,
+ endColumn: position.column,
+ });
+
+ const match = line.match(/\/([\w:\/\-.]*)$/);
+ if (!match) return { suggestions: [] };
+
+ const typed = match[1];
+ const startCol = position.column - typed.length - 1;
+ const range = {
+ startLineNumber: position.lineNumber,
+ endLineNumber: position.lineNumber,
+ startColumn: startCol,
+ endColumn: position.column,
+ };
+
+ const CIK = monaco.languages.CompletionItemKind;
+
+ // Sugestões de prefixos
+ const prefixSuggestions = PREFIXES.map(p => ({
+ label: p.label,
+ kind: CIK.Reference,
+ detail: p.detail,
+ insertText: p.label,
+ sortText: "0_" + p.label,
+ range,
+ }));
+
+ // Sugestões de skills existentes quando digita /skill/...
+ const skillSuggestions: any[] = [];
+ if (typed.startsWith("skill/") || typed.startsWith("skill:")) {
+ const afterSkill = typed.replace(/^skill(:[^/]+)?\//, "");
+ skillsRef.current.forEach(s => {
+ const ref = `/skill/${s.namespace}/${s.slug}`;
+ if (!afterSkill || s.slug.includes(afterSkill) || s.name.toLowerCase().includes(afterSkill)) {
+ skillSuggestions.push({
+ label: ref,
+ kind: CIK.Variable,
+ detail: s.description ?? s.name,
+ documentation: `namespace: ${s.namespace} | status: ${s.status}`,
+ insertText: ref,
+ sortText: "1_" + ref,
+ range,
+ });
+ }
+ });
+ }
+
+ return { suggestions: [...prefixSuggestions, ...skillSuggestions] };
+ },
+ });
+ }
+
// ── Render ─────────────────────────────────────────────────────────────────
return (
@@ -529,6 +614,7 @@ export default function Skills() {
defaultLanguage="markdown"
value={form.body}
onChange={v => setForm(f => ({ ...f, body: v ?? "" }))}
+ onMount={handleBodyEditorMount}
theme="vs-dark"
options={{
fontSize: 13,
@@ -537,6 +623,8 @@ export default function Skills() {
lineNumbers: "on",
scrollBeyondLastLine: false,
padding: { top: 8 },
+ quickSuggestions: { other: true, comments: true, strings: true },
+ suggestOnTriggerCharacters: true,
}}
/>