feat(ai): keep generated edge IDs out of create_diagram_memo Agent input
This commit is contained in:
1 parent
b41fb366eb
commit
6e0d9c570c
4 files changed
+42
-7
No files matched your search
@@ -54,7 +54,7 @@ const TOOL_HINTS: Record<string, string> = {
|
||||
search_memos: " Searches note titles and bodies, not notebook names. query is optional. For recently created or added notes, pass createdAfter (YYYY-MM-DD or ISO date-time) and omit query; never put this week/最近/新增 in query. For recently edited notes, use updatedAfter. Do not pass notebookId unless find_notebooks or list_notebooks returned it. For notes in a named notebook, find_notebooks then list_memos. If hasMore is true, say the list is incomplete.",
|
||||
list_tags: " Use this when the user names a tag.",
|
||||
create_memo: " For prose Markdown notes only. Never use this for 思维导图/mind maps, 流程图/flowcharts, or 架构图; use create_diagram_memo.",
|
||||
create_diagram_memo: " Create an editable visual diagram note. kind=mind-map for 思维导图/mind map, flowchart for 流程图, architecture for 架构图. For mind maps, give a root and children with parentId; omit node type. If the user did not name a notebook, use the open notebook id from Focus DATA. Build nodes from the open note body in Focus DATA when the user refers to this note.",
|
||||
create_diagram_memo: " Create an editable visual diagram note. kind=mind-map for 思维导图/mind map, flowchart for 流程图, architecture for 架构图. Omit edge ids; EdgeEver generates them. For mind maps, give a root and children with parentId; omit node type. If the user did not name a notebook, use the open notebook id from Focus DATA. Build nodes from the open note body in Focus DATA when the user refers to this note.",
|
||||
get_diagram: " Read an existing editable diagram as a semantic graph. Call this before update_diagram. Do not use get_memo when you only need the diagram structure.",
|
||||
update_diagram: " Edit an existing diagram after get_diagram. Pass expectedRevision from get_diagram. Use add_node, update_node, remove_node, add_edge, update_edge, or remove_edge. Do not create a new diagram unless the user asked for a new note.",
|
||||
use_note_template: " Create a new memo from a template. If the user did not name a notebook, use the open notebook id from Focus DATA.",
|
||||
|
||||
@@ -219,6 +219,29 @@ describe("shared companion MCP adapter", () => {
|
||||
.toContain("eval");
|
||||
expect(f.sqlite.query("SELECT COUNT(*) AS n FROM companion_actions").get().n).toBe(0);
|
||||
});
|
||||
test("create_diagram_memo keeps generated edge IDs out of Agent input", async () => {
|
||||
const f = await setup();
|
||||
const tools = createCompanionTools({ ...f, scope, signal: new AbortController().signal, assertActive: async () => {}, sources: [] });
|
||||
const input = {
|
||||
notebookId: "ideas",
|
||||
title: "Service architecture",
|
||||
kind: "architecture",
|
||||
nodes: [
|
||||
{ id: "web", label: "Web", type: "frontend" },
|
||||
{ id: "api", label: "API", type: "service" },
|
||||
],
|
||||
edges: [{ source: "web", target: "api", type: "request" }],
|
||||
};
|
||||
expect(() => validateCompanionTool("create_diagram_memo", {
|
||||
...input,
|
||||
edges: [{ id: "edge-1", source: "web", target: "api", type: "request" }],
|
||||
})).toThrow();
|
||||
const created = await tools.create_diagram_memo.execute(input);
|
||||
expect(created).toMatchObject({ applied: true, diagramKind: "architecture", nodeCount: 2 });
|
||||
const diagram = parseDiagramDocument((await getMemoDetail(f.db, scope.workspaceId, created.id)).contentMarkdown);
|
||||
expect(diagram.edges).toMatchObject([{ source: "web", target: "api", kind: "request" }]);
|
||||
expect(diagram.edges[0].id).toBeTruthy();
|
||||
});
|
||||
test("note templates and AI instructions execute immediately including deletes", async () => {
|
||||
const f = await setup();
|
||||
const tools = createCompanionTools({ ...f, scope, signal: new AbortController().signal, assertActive: async () => {}, sources: [] });
|
||||
|
||||
@@ -66,6 +66,7 @@ describe("MCP tool catalog", () => {
|
||||
});
|
||||
expect(byName.get("create_diagram_memo")?.inputSchema.properties.nodes.items.properties.x).toBeUndefined();
|
||||
expect(byName.get("create_diagram_memo")?.inputSchema.properties.nodes.items.properties.width).toBeUndefined();
|
||||
expect(byName.get("create_diagram_memo")?.inputSchema.properties.edges.items.properties.id).toBeUndefined();
|
||||
expect(byName.get("get_diagram")?.annotations).toMatchObject({
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
@@ -98,6 +99,8 @@ describe("MCP tool catalog", () => {
|
||||
});
|
||||
expect(byName.get("update_diagram")?.inputSchema.properties.operations.items.oneOf).toBeUndefined();
|
||||
expect(byName.get("update_diagram")?.inputSchema.properties.operations.items.properties.op.const).toBeUndefined();
|
||||
expect(byName.get("update_diagram")?.inputSchema.properties.operations.items.properties.edge.properties.id)
|
||||
.toEqual({ type: "string", minLength: 1, maxLength: 100 });
|
||||
expect(byName.get("rename_notebook")?.annotations).toMatchObject({
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
|
||||
@@ -33,17 +33,26 @@ const diagramNodeSchema = {
|
||||
properties: diagramNodeProperties,
|
||||
};
|
||||
|
||||
const diagramEdgeProperties = {
|
||||
source: { type: "string", minLength: 1, maxLength: 100 },
|
||||
target: { type: "string", minLength: 1, maxLength: 100 },
|
||||
label: { type: "string", maxLength: 500 },
|
||||
type: { type: "string", enum: ["dependency", "request", "async", "data"] },
|
||||
bidirectional: { type: "boolean" },
|
||||
};
|
||||
|
||||
const diagramEdgeSchema = {
|
||||
type: "object",
|
||||
required: ["source", "target"],
|
||||
additionalProperties: false,
|
||||
properties: diagramEdgeProperties,
|
||||
};
|
||||
|
||||
const mutableDiagramEdgeSchema = {
|
||||
...diagramEdgeSchema,
|
||||
properties: {
|
||||
id: { type: "string", minLength: 1, maxLength: 100 },
|
||||
source: { type: "string", minLength: 1, maxLength: 100 },
|
||||
target: { type: "string", minLength: 1, maxLength: 100 },
|
||||
label: { type: "string", maxLength: 500 },
|
||||
type: { type: "string", enum: ["dependency", "request", "async", "data"] },
|
||||
bidirectional: { type: "boolean" },
|
||||
...diagramEdgeProperties,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -201,7 +210,7 @@ const MCP_TOOL_DEFINITIONS = [
|
||||
},
|
||||
node: diagramNodeSchema,
|
||||
nodeId: { type: "string", minLength: 1 },
|
||||
edge: diagramEdgeSchema,
|
||||
edge: mutableDiagramEdgeSchema,
|
||||
edgeId: { type: "string", minLength: 1 },
|
||||
changes: {
|
||||
type: "object",
|
||||
|
||||
Reference in new issue
Block a user