crow-central-agency 0.25.13 → 0.25.14
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/_shared/schemas/artifact.schema.js +1 -0
- package/dist/bootstrap.js +1 -0
- package/dist/core/error/app-error.types.js +1 -0
- package/dist/mcp/artifacts/artifacts-mcp-server-utils.js +70 -1
- package/dist/mcp/artifacts/artifacts-mcp-server.js +14 -0
- package/dist/mcp/artifacts/delete-circle-artifact.js +1 -1
- package/dist/mcp/artifacts/edit-artifact.js +66 -0
- package/dist/mcp/artifacts/edit-circle-artifact.js +70 -0
- package/dist/mcp/artifacts/find-content-in-artifact.js +37 -0
- package/dist/mcp/artifacts/find-content-in-circle-artifact.js +41 -0
- package/dist/mcp/artifacts/list-artifacts.js +16 -5
- package/dist/mcp/artifacts/list-circle-artifacts.js +15 -4
- package/dist/mcp/artifacts/read-artifact.js +3 -4
- package/dist/mcp/artifacts/read-circle-artifact.js +2 -3
- package/dist/mcp/artifacts/write-artifact.js +9 -4
- package/dist/mcp/artifacts/write-circle-artifact.js +11 -6
- package/dist/mcp/tool-utils.js +1 -1
- package/dist/public/assets/{architectureDiagram-3BPJPVTR-DbWcWOBY.js → architectureDiagram-3BPJPVTR-BfGAPq2z.js} +1 -1
- package/dist/public/assets/{chunk-727SXJPM-ClhRzWEk.js → chunk-727SXJPM-DuZqS4MG.js} +1 -1
- package/dist/public/assets/{chunk-AQP2D5EJ-CYZG32-Q.js → chunk-AQP2D5EJ-Cn4xG-Ry.js} +1 -1
- package/dist/public/assets/{classDiagram-v2-Q7XG4LA2-D3t5sS78.js → classDiagram-4FO5ZUOK-BUl9xlUL.js} +1 -1
- package/dist/public/assets/{classDiagram-4FO5ZUOK-urSTQh2x.js → classDiagram-v2-Q7XG4LA2-D1CQ-gDz.js} +1 -1
- package/dist/public/assets/{diagram-2AECGRRQ-CsvYtH42.js → diagram-2AECGRRQ-CX8foJPq.js} +1 -1
- package/dist/public/assets/{diagram-5GNKFQAL-DKBa5qvt.js → diagram-5GNKFQAL-DA78CQem.js} +1 -1
- package/dist/public/assets/{diagram-LMA3HP47-Dn8u-OFi.js → diagram-LMA3HP47-xHMjy8aE.js} +1 -1
- package/dist/public/assets/{diagram-OG6HWLK6-vMG-8YUk.js → diagram-OG6HWLK6-DlRO3Ayl.js} +1 -1
- package/dist/public/assets/{erDiagram-TEJ5UH35-BQ9XtJBn.js → erDiagram-TEJ5UH35-CZqcN6px.js} +1 -1
- package/dist/public/assets/{flowDiagram-I6XJVG4X-CTSa0pWx.js → flowDiagram-I6XJVG4X-DlOZbuGG.js} +1 -1
- package/dist/public/assets/{index-CoWuzbp6.js → index-DmdHg7Be.js} +4 -4
- package/dist/public/assets/{infoDiagram-5YYISTIA-D9OLId3y.js → infoDiagram-5YYISTIA-BW8XbP1C.js} +1 -1
- package/dist/public/assets/{ishikawaDiagram-YF4QCWOH-DgrXdDFX.js → ishikawaDiagram-YF4QCWOH-BA-_CtvR.js} +1 -1
- package/dist/public/assets/{kanban-definition-UN3LZRKU-BHDPdLIR.js → kanban-definition-UN3LZRKU-C8TVdEvL.js} +1 -1
- package/dist/public/assets/{mindmap-definition-RKZ34NQL-BWzeH6a0.js → mindmap-definition-RKZ34NQL-DZkPdbNS.js} +1 -1
- package/dist/public/assets/{pieDiagram-4H26LBE5-B-Yr1GRG.js → pieDiagram-4H26LBE5-BBx1uSsh.js} +1 -1
- package/dist/public/assets/{requirementDiagram-4Y6WPE33-C0ukX7lT.js → requirementDiagram-4Y6WPE33-C35Fc9Mo.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-3UESZ5HK-B9Zy5t1a.js → sequenceDiagram-3UESZ5HK-DO2aCVNa.js} +1 -1
- package/dist/public/assets/{stateDiagram-AJRCARHV-C5Spt8v4.js → stateDiagram-AJRCARHV-Dtf4GcS-.js} +1 -1
- package/dist/public/assets/{stateDiagram-v2-BHNVJYJU-BirPm6MH.js → stateDiagram-v2-BHNVJYJU-BWXbQBcB.js} +1 -1
- package/dist/public/assets/{timeline-definition-PNZ67QCA--qBnNF5F.js → timeline-definition-PNZ67QCA-ar2DXF9S.js} +1 -1
- package/dist/public/assets/{vennDiagram-CIIHVFJN-IE4AXUoa.js → vennDiagram-CIIHVFJN-B7tt3Tsv.js} +1 -1
- package/dist/public/assets/{wardleyDiagram-YWT4CUSO-DHgyly1b.js → wardleyDiagram-YWT4CUSO-CdG42xCG.js} +1 -1
- package/dist/public/assets/{xychartDiagram-2RQKCTM6-uC6o17p1.js → xychartDiagram-2RQKCTM6-CMAXWBmj.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/routes/artifact.routes.js +2 -4
- package/dist/server/error-handler.js +1 -0
- package/dist/services/artifact/artifact-manager.js +119 -9
- package/dist/services/artifact/artifact-tags.js +9 -0
- package/dist/services/runtime/agent-runtime-manager.js +10 -4
- package/package.json +3 -3
|
@@ -43,6 +43,7 @@ export const ArtifactMetadataSchema = z.object({
|
|
|
43
43
|
entityId: z.string(),
|
|
44
44
|
entityType: EntityTypeSchema,
|
|
45
45
|
size: z.number(),
|
|
46
|
+
tags: z.array(z.string()).optional(),
|
|
46
47
|
createdTimestamp: z.number(),
|
|
47
48
|
updatedTimestamp: z.number(),
|
|
48
49
|
createdBy: AgentTaskSourceSchema,
|
package/dist/bootstrap.js
CHANGED
|
@@ -147,6 +147,7 @@ export async function bootstrap(options) {
|
|
|
147
147
|
isConfigurable: googleContactsMcpDefinition.isConfigurable,
|
|
148
148
|
displayName: googleContactsMcpDefinition.displayName,
|
|
149
149
|
});
|
|
150
|
+
await runtimeManager.startRecovery();
|
|
150
151
|
// Start scheduler
|
|
151
152
|
crowScheduler.start();
|
|
152
153
|
// Create Fastify server
|
|
@@ -18,6 +18,7 @@ export const APP_ERROR_CODES = {
|
|
|
18
18
|
INVALID_STATE_TRANSITION: "invalid_state_transition",
|
|
19
19
|
PATH_TRAVERSAL: "path_traversal",
|
|
20
20
|
INVALID_FILENAME: "invalid_filename",
|
|
21
|
+
CONFLICT: "conflict",
|
|
21
22
|
MCP_CONFIG_NOT_FOUND: "mcp_config_not_found",
|
|
22
23
|
MCP_ERROR: "mcp_error",
|
|
23
24
|
SDK_ERROR: "sdk_error",
|
|
@@ -2,8 +2,50 @@ import path from "node:path";
|
|
|
2
2
|
import { ARTIFACT_CONTENT_TYPE, ARTIFACT_TYPE } from "../../_shared/index.js";
|
|
3
3
|
import { formatLocalDateTime } from "../../utils/date-utils.js";
|
|
4
4
|
import { processTextContent, textToolResult } from "../tool-utils.js";
|
|
5
|
+
import { last } from "es-toolkit";
|
|
5
6
|
export const ARTIFACT_TYPE_VALUES = Object.values(ARTIFACT_TYPE);
|
|
6
7
|
export const ARTIFACT_CONTENT_TYPE_VALUES = Object.values(ARTIFACT_CONTENT_TYPE);
|
|
8
|
+
export const EDIT_ARTIFACT_MODE = {
|
|
9
|
+
INSERT: "insert",
|
|
10
|
+
REPLACE: "replace",
|
|
11
|
+
};
|
|
12
|
+
export const EDIT_ARTIFACT_MODE_VALUES = Object.values(EDIT_ARTIFACT_MODE);
|
|
13
|
+
/**
|
|
14
|
+
* Apply an insert/replace line edit. Lines are 1-based; endLine is inclusive (used for replace only).
|
|
15
|
+
* Throws when line numbers are out of range or endLine is missing/invalid for replace.
|
|
16
|
+
*/
|
|
17
|
+
export function applyLineEdit(existingContent, content, mode, startLine, endLine) {
|
|
18
|
+
const existingLines = existingContent.split("\n");
|
|
19
|
+
const totalLines = existingLines.length;
|
|
20
|
+
if (startLine < 1) {
|
|
21
|
+
throw new Error("startLine must be a positive integer starting from 1.");
|
|
22
|
+
}
|
|
23
|
+
// allow adding a line after last line
|
|
24
|
+
if (startLine > totalLines + 1) {
|
|
25
|
+
throw new Error(`Starting line (${startLine}) exceeds the total number of lines (${totalLines}).`);
|
|
26
|
+
}
|
|
27
|
+
if (mode === EDIT_ARTIFACT_MODE.REPLACE) {
|
|
28
|
+
if (endLine === undefined) {
|
|
29
|
+
throw new Error("endLine is required for 'replace' mode.");
|
|
30
|
+
}
|
|
31
|
+
if (endLine > totalLines) {
|
|
32
|
+
throw new Error(`Ending line (${endLine}) exceeds the total number of lines (${totalLines}).`);
|
|
33
|
+
}
|
|
34
|
+
if (endLine < startLine) {
|
|
35
|
+
throw new Error(`Ending line (${endLine}) must be greater than or equal to starting line (${startLine}).`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const preContent = existingLines.slice(0, startLine - 1);
|
|
39
|
+
const effectiveEnd = mode === EDIT_ARTIFACT_MODE.REPLACE && endLine !== undefined ? endLine : startLine - 1;
|
|
40
|
+
const postContent = existingLines.slice(effectiveEnd);
|
|
41
|
+
const newContent = content.split("\n");
|
|
42
|
+
const lastLine = last(newContent);
|
|
43
|
+
if (lastLine !== undefined && !lastLine) {
|
|
44
|
+
newContent.splice(-1, 1);
|
|
45
|
+
}
|
|
46
|
+
const updatedContent = preContent.concat(newContent).concat(postContent).join("\n");
|
|
47
|
+
return updatedContent;
|
|
48
|
+
}
|
|
7
49
|
/** Image extensions that Claude can process natively via base64 */
|
|
8
50
|
const SUPPORTED_IMAGE_MIME = {
|
|
9
51
|
".jpg": "image/jpeg",
|
|
@@ -18,8 +60,11 @@ const PDF_MIME = "application/pdf";
|
|
|
18
60
|
export function buildReadArtifactResult(content, metadata, userTimezone, lineOptions) {
|
|
19
61
|
const header = [
|
|
20
62
|
`--- METADATA ---`,
|
|
21
|
-
`[Type: ${metadata.type} | Content: ${metadata.contentType} | Modified: ${formatLocalDateTime(new Date(metadata.updatedTimestamp), userTimezone)}]`,
|
|
63
|
+
`[Type: ${metadata.type} | Content: ${metadata.contentType} | Modified: ${formatLocalDateTime(new Date(metadata.updatedTimestamp), userTimezone)} | Version: ${metadata.updatedTimestamp}]`,
|
|
22
64
|
];
|
|
65
|
+
if (metadata.tags?.length) {
|
|
66
|
+
header.push(`[Tags: ${metadata.tags.join(", ")}]`);
|
|
67
|
+
}
|
|
23
68
|
if (typeof content === "string" || metadata.contentType === ARTIFACT_CONTENT_TYPE.TEXT) {
|
|
24
69
|
const rawText = typeof content === "string" ? content : content.toString("utf-8");
|
|
25
70
|
const processed = processTextContent(rawText, lineOptions);
|
|
@@ -55,4 +100,28 @@ export function buildReadArtifactResult(content, metadata, userTimezone, lineOpt
|
|
|
55
100
|
`[Binary artifact: ${metadata.contentType} content (${metadata.size} bytes). This binary format is not supported for interpretation.]`,
|
|
56
101
|
]);
|
|
57
102
|
}
|
|
103
|
+
/** Build the MCP text result for a find-content search. Dedupes by line; honors an optional limit on lines. */
|
|
104
|
+
export function buildFindContentResult(filename, query, result, limit) {
|
|
105
|
+
if (!result.found) {
|
|
106
|
+
return textToolResult([`No matches found for "${query}" in ${filename}.`]);
|
|
107
|
+
}
|
|
108
|
+
const seenLines = new Set();
|
|
109
|
+
const shownLines = [];
|
|
110
|
+
for (const match of result.matches) {
|
|
111
|
+
if (seenLines.has(match.lineNumber)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
seenLines.add(match.lineNumber);
|
|
115
|
+
if (limit === undefined || shownLines.length < limit) {
|
|
116
|
+
shownLines.push(`[L${match.lineNumber}] ${match.lineContent}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const truncated = limit !== undefined && seenLines.size > limit;
|
|
120
|
+
const lines = [
|
|
121
|
+
`Found ${seenLines.size} matching line(s) for "${query}" in ${filename}${truncated ? ` (showing first ${limit})` : ""}:`,
|
|
122
|
+
"Lines are prefixed with [LNNN] markers. These markers are NOT part of the line content.",
|
|
123
|
+
...shownLines,
|
|
124
|
+
];
|
|
125
|
+
return textToolResult(lines);
|
|
126
|
+
}
|
|
58
127
|
//# sourceMappingURL=artifacts-mcp-server-utils.js.map
|
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { getWriteArtifactToolConfig } from "./write-artifact.js";
|
|
3
|
+
import { getEditArtifactToolConfig } from "./edit-artifact.js";
|
|
3
4
|
import { getReadArtifactToolConfig } from "./read-artifact.js";
|
|
4
5
|
import { getListArtifactsToolConfig } from "./list-artifacts.js";
|
|
5
6
|
import { getDeleteArtifactToolConfig } from "./delete-artifact.js";
|
|
7
|
+
import { getFindContentInArtifactToolConfig } from "./find-content-in-artifact.js";
|
|
6
8
|
import { getWriteCircleArtifactToolConfig } from "./write-circle-artifact.js";
|
|
9
|
+
import { getEditCircleArtifactToolConfig } from "./edit-circle-artifact.js";
|
|
7
10
|
import { getReadCircleArtifactToolConfig } from "./read-circle-artifact.js";
|
|
8
11
|
import { getListCircleArtifactsToolConfig } from "./list-circle-artifacts.js";
|
|
9
12
|
import { getDeleteCircleArtifactToolConfig } from "./delete-circle-artifact.js";
|
|
13
|
+
import { getFindContentInCircleArtifactToolConfig } from "./find-content-in-circle-artifact.js";
|
|
10
14
|
export const ARTIFACTS_MCP_SERVER_NAME = "crow-artifacts";
|
|
11
15
|
export function createArtifactsMcpServer(agentId, artifactManager, registry, circleManager, sensorManager) {
|
|
12
16
|
const writeArtifact = getWriteArtifactToolConfig(agentId, artifactManager, sensorManager);
|
|
17
|
+
const editArtifact = getEditArtifactToolConfig(agentId, artifactManager, sensorManager);
|
|
13
18
|
const readArtifact = getReadArtifactToolConfig(agentId, artifactManager, registry, circleManager, sensorManager);
|
|
14
19
|
const listArtifacts = getListArtifactsToolConfig(agentId, artifactManager, registry, circleManager, sensorManager);
|
|
15
20
|
const deleteArtifact = getDeleteArtifactToolConfig(agentId, artifactManager);
|
|
21
|
+
const findContentInArtifact = getFindContentInArtifactToolConfig(agentId, artifactManager);
|
|
16
22
|
const writeCircleArtifact = getWriteCircleArtifactToolConfig(agentId, artifactManager, sensorManager);
|
|
23
|
+
const editCircleArtifact = getEditCircleArtifactToolConfig(agentId, artifactManager, sensorManager);
|
|
17
24
|
const readCircleArtifact = getReadCircleArtifactToolConfig(agentId, artifactManager, sensorManager);
|
|
18
25
|
const listCircleArtifacts = getListCircleArtifactsToolConfig(agentId, artifactManager, sensorManager);
|
|
19
26
|
const deleteCircleArtifact = getDeleteCircleArtifactToolConfig(agentId, artifactManager);
|
|
27
|
+
const findContentInCircleArtifact = getFindContentInCircleArtifactToolConfig(agentId, artifactManager);
|
|
20
28
|
return createSdkMcpServer({
|
|
21
29
|
name: ARTIFACTS_MCP_SERVER_NAME,
|
|
22
30
|
tools: [
|
|
23
31
|
tool(writeArtifact.name, writeArtifact.description, writeArtifact.inputSchema, writeArtifact.handler, {
|
|
24
32
|
annotations: writeArtifact.annotations,
|
|
25
33
|
}),
|
|
34
|
+
tool(editArtifact.name, editArtifact.description, editArtifact.inputSchema, editArtifact.handler, {
|
|
35
|
+
annotations: editArtifact.annotations,
|
|
36
|
+
}),
|
|
26
37
|
tool(readArtifact.name, readArtifact.description, readArtifact.inputSchema, readArtifact.handler, {
|
|
27
38
|
annotations: readArtifact.annotations,
|
|
28
39
|
}),
|
|
@@ -32,10 +43,13 @@ export function createArtifactsMcpServer(agentId, artifactManager, registry, cir
|
|
|
32
43
|
tool(deleteArtifact.name, deleteArtifact.description, deleteArtifact.inputSchema, deleteArtifact.handler, {
|
|
33
44
|
annotations: deleteArtifact.annotations,
|
|
34
45
|
}),
|
|
46
|
+
tool(findContentInArtifact.name, findContentInArtifact.description, findContentInArtifact.inputSchema, findContentInArtifact.handler, { annotations: findContentInArtifact.annotations }),
|
|
35
47
|
tool(writeCircleArtifact.name, writeCircleArtifact.description, writeCircleArtifact.inputSchema, writeCircleArtifact.handler, { annotations: writeCircleArtifact.annotations }),
|
|
48
|
+
tool(editCircleArtifact.name, editCircleArtifact.description, editCircleArtifact.inputSchema, editCircleArtifact.handler, { annotations: editCircleArtifact.annotations }),
|
|
36
49
|
tool(readCircleArtifact.name, readCircleArtifact.description, readCircleArtifact.inputSchema, readCircleArtifact.handler, { annotations: readCircleArtifact.annotations }),
|
|
37
50
|
tool(listCircleArtifacts.name, listCircleArtifacts.description, listCircleArtifacts.inputSchema, listCircleArtifacts.handler, { annotations: listCircleArtifacts.annotations }),
|
|
38
51
|
tool(deleteCircleArtifact.name, deleteCircleArtifact.description, deleteCircleArtifact.inputSchema, deleteCircleArtifact.handler, { annotations: deleteCircleArtifact.annotations }),
|
|
52
|
+
tool(findContentInCircleArtifact.name, findContentInCircleArtifact.description, findContentInCircleArtifact.inputSchema, findContentInCircleArtifact.handler, { annotations: findContentInCircleArtifact.annotations }),
|
|
39
53
|
],
|
|
40
54
|
});
|
|
41
55
|
}
|
|
@@ -9,7 +9,7 @@ export function getDeleteCircleArtifactToolConfig(agentId, artifactManager) {
|
|
|
9
9
|
};
|
|
10
10
|
const handler = async ({ circle_id, filename }) => {
|
|
11
11
|
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
12
|
-
return textToolResult(["
|
|
12
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
13
13
|
}
|
|
14
14
|
try {
|
|
15
15
|
const metadata = await artifactManager.getCircleArtifactMetadata(circle_id, filename);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ARTIFACT_CONTENT_TYPE } from "../../_shared/index.js";
|
|
3
|
+
import { getErrorToolResult, textToolResult } from "../tool-utils.js";
|
|
4
|
+
import { formatLocalDateTime } from "../../utils/date-utils.js";
|
|
5
|
+
import { applyLineEdit, EDIT_ARTIFACT_MODE, EDIT_ARTIFACT_MODE_VALUES } from "./artifacts-mcp-server-utils.js";
|
|
6
|
+
export const EDIT_ARTIFACT_TOOL_NAME = "edit_artifact";
|
|
7
|
+
export function getEditArtifactToolConfig(agentId, artifactManager, sensorManager) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
filename: z.string().describe("Name of an existing TEXT artifact to edit."),
|
|
10
|
+
mode: z
|
|
11
|
+
.enum(EDIT_ARTIFACT_MODE_VALUES)
|
|
12
|
+
.describe("Edit mode. 'insert': insert new lines before startLine. 'replace': replace lines startLine..endLine inclusive. Both require a TEXT artifact."),
|
|
13
|
+
content: z.string().describe("The new text to insert or use as replacement."),
|
|
14
|
+
startLine: z
|
|
15
|
+
.number()
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe("1-based line number. For 'insert', new lines are placed before this line. For 'replace', this is the first line replaced."),
|
|
18
|
+
endLine: z
|
|
19
|
+
.number()
|
|
20
|
+
.min(1)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("1-based inclusive last line to replace. Required for 'replace' mode. Ignored for 'insert'."),
|
|
23
|
+
version: z
|
|
24
|
+
.number()
|
|
25
|
+
.describe("The Version value from your most recent read of this artifact (the [Version: ...] token)."),
|
|
26
|
+
addTags: z.array(z.string()).optional().describe("Optional tags to add to the artifact."),
|
|
27
|
+
removeTags: z.array(z.string()).optional().describe("Optional tags to remove from the artifact."),
|
|
28
|
+
};
|
|
29
|
+
const handler = async ({ filename: rawFilename, content, mode, startLine, endLine, version, addTags, removeTags, }) => {
|
|
30
|
+
const filename = rawFilename.trim();
|
|
31
|
+
try {
|
|
32
|
+
if (mode === EDIT_ARTIFACT_MODE.REPLACE && endLine === undefined) {
|
|
33
|
+
throw new Error("endLine is required for 'replace' mode.");
|
|
34
|
+
}
|
|
35
|
+
const { content: existingContent, metadata } = await artifactManager.readArtifact(agentId, filename);
|
|
36
|
+
if (metadata.contentType !== ARTIFACT_CONTENT_TYPE.TEXT || typeof existingContent !== "string") {
|
|
37
|
+
throw new Error(`edit_artifact only supports TEXT artifacts (${metadata.filename} is ${metadata.contentType}). Use write_artifact to replace the file.`);
|
|
38
|
+
}
|
|
39
|
+
const nextContent = applyLineEdit(existingContent, content, mode, startLine, endLine);
|
|
40
|
+
const updated = await artifactManager.updateArtifact(agentId, filename, {
|
|
41
|
+
content: nextContent,
|
|
42
|
+
addTags,
|
|
43
|
+
removeTags,
|
|
44
|
+
expectedUpdatedTimestamp: version,
|
|
45
|
+
});
|
|
46
|
+
const userTimezone = await sensorManager.getUserTimezone();
|
|
47
|
+
const editNote = mode === EDIT_ARTIFACT_MODE.REPLACE
|
|
48
|
+
? `replaced lines ${startLine}-${endLine}`
|
|
49
|
+
: `inserted at line ${startLine}`;
|
|
50
|
+
return textToolResult([
|
|
51
|
+
`Artifact edited: ${updated.filename} (${editNote}, size: ${updated.size} bytes, modified: ${formatLocalDateTime(new Date(updated.updatedTimestamp), userTimezone)}). Re-read the artifact before the next edit; line numbers and Version are now stale.`,
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return getErrorToolResult(error, "Failed to edit artifact.");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const config = {
|
|
59
|
+
name: EDIT_ARTIFACT_TOOL_NAME,
|
|
60
|
+
description: "Surgically modify a TEXT artifact you own by inserting or replacing a range of lines. Use write_artifact to replace the full file or to write non-TEXT content.",
|
|
61
|
+
inputSchema,
|
|
62
|
+
handler,
|
|
63
|
+
};
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=edit-artifact.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ARTIFACT_CONTENT_TYPE } from "../../_shared/index.js";
|
|
3
|
+
import { getErrorToolResult, textToolResult } from "../tool-utils.js";
|
|
4
|
+
import { formatLocalDateTime } from "../../utils/date-utils.js";
|
|
5
|
+
import { applyLineEdit, EDIT_ARTIFACT_MODE, EDIT_ARTIFACT_MODE_VALUES } from "./artifacts-mcp-server-utils.js";
|
|
6
|
+
export const EDIT_CIRCLE_ARTIFACT_TOOL_NAME = "edit_circle_artifact";
|
|
7
|
+
export function getEditCircleArtifactToolConfig(agentId, artifactManager, sensorManager) {
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
circle_id: z.string().describe("The circle ID that owns the artifact."),
|
|
10
|
+
filename: z.string().describe("Name of an existing TEXT circle artifact to edit."),
|
|
11
|
+
mode: z
|
|
12
|
+
.enum(EDIT_ARTIFACT_MODE_VALUES)
|
|
13
|
+
.describe("Edit mode. 'insert': insert new lines before startLine. 'replace': replace lines startLine..endLine inclusive. Both require a TEXT artifact."),
|
|
14
|
+
content: z.string().describe("The new text to insert or use as replacement."),
|
|
15
|
+
startLine: z
|
|
16
|
+
.number()
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe("1-based line number. For 'insert', new lines are placed before this line. For 'replace', this is the first line replaced."),
|
|
19
|
+
endLine: z
|
|
20
|
+
.number()
|
|
21
|
+
.min(1)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("1-based inclusive last line to replace. Required for 'replace' mode. Ignored for 'insert'."),
|
|
24
|
+
version: z
|
|
25
|
+
.number()
|
|
26
|
+
.describe("The Version value from your most recent read of this artifact (the [Version: ...] token)."),
|
|
27
|
+
addTags: z.array(z.string()).optional().describe("Optional tags to add to the artifact."),
|
|
28
|
+
removeTags: z.array(z.string()).optional().describe("Optional tags to remove from the artifact."),
|
|
29
|
+
};
|
|
30
|
+
const handler = async ({ circle_id, filename: rawFilename, content, mode, startLine, endLine, version, addTags, removeTags, }) => {
|
|
31
|
+
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
32
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
33
|
+
}
|
|
34
|
+
const filename = rawFilename.trim();
|
|
35
|
+
try {
|
|
36
|
+
if (mode === EDIT_ARTIFACT_MODE.REPLACE && endLine === undefined) {
|
|
37
|
+
throw new Error("endLine is required for 'replace' mode.");
|
|
38
|
+
}
|
|
39
|
+
const { content: existingContent, metadata } = await artifactManager.readCircleArtifact(circle_id, filename);
|
|
40
|
+
if (metadata.contentType !== ARTIFACT_CONTENT_TYPE.TEXT || typeof existingContent !== "string") {
|
|
41
|
+
throw new Error(`edit_circle_artifact only supports TEXT artifacts (${metadata.filename} is ${metadata.contentType}). Use write_circle_artifact to replace the file.`);
|
|
42
|
+
}
|
|
43
|
+
const nextContent = applyLineEdit(existingContent, content, mode, startLine, endLine);
|
|
44
|
+
const updated = await artifactManager.updateCircleArtifact(circle_id, filename, {
|
|
45
|
+
content: nextContent,
|
|
46
|
+
addTags,
|
|
47
|
+
removeTags,
|
|
48
|
+
expectedUpdatedTimestamp: version,
|
|
49
|
+
});
|
|
50
|
+
const userTimezone = await sensorManager.getUserTimezone();
|
|
51
|
+
const editNote = mode === EDIT_ARTIFACT_MODE.REPLACE
|
|
52
|
+
? `replaced lines ${startLine}-${endLine}`
|
|
53
|
+
: `inserted at line ${startLine}`;
|
|
54
|
+
return textToolResult([
|
|
55
|
+
`Circle artifact edited: ${updated.filename} (circle: ${circle_id}, ${editNote}, size: ${updated.size} bytes, modified: ${formatLocalDateTime(new Date(updated.updatedTimestamp), userTimezone)}). Re-read the artifact before the next edit; line numbers and Version are now stale.`,
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return getErrorToolResult(error, "Failed to edit circle artifact.");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const config = {
|
|
63
|
+
name: EDIT_CIRCLE_ARTIFACT_TOOL_NAME,
|
|
64
|
+
description: "Surgically modify a TEXT circle artifact by inserting or replacing a range of lines. Only direct members of the circle can edit. Use write_circle_artifact to replace the full file or to write non-TEXT content.",
|
|
65
|
+
inputSchema,
|
|
66
|
+
handler,
|
|
67
|
+
};
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=edit-circle-artifact.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getErrorToolResult, textToolResult } from "../tool-utils.js";
|
|
3
|
+
import { buildFindContentResult } from "./artifacts-mcp-server-utils.js";
|
|
4
|
+
export const FIND_CONTENT_IN_ARTIFACT_TOOL_NAME = "find_content_in_artifact";
|
|
5
|
+
export function getFindContentInArtifactToolConfig(agentId, artifactManager) {
|
|
6
|
+
const inputSchema = {
|
|
7
|
+
filename: z.string().describe("Name of an existing TEXT artifact to search."),
|
|
8
|
+
query: z.string().min(1).describe("Substring to search for. Case-insensitive."),
|
|
9
|
+
startLine: z
|
|
10
|
+
.number()
|
|
11
|
+
.min(1)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Optional. 1-based line number to start searching from. Defaults to 1."),
|
|
14
|
+
limit: z.number().min(1).optional().describe("Optional. Maximum number of matches to return."),
|
|
15
|
+
};
|
|
16
|
+
const handler = async ({ filename: rawFilename, query, startLine, limit }) => {
|
|
17
|
+
const filename = rawFilename.trim();
|
|
18
|
+
try {
|
|
19
|
+
if (!query) {
|
|
20
|
+
return textToolResult(["Search query must not be empty."], true);
|
|
21
|
+
}
|
|
22
|
+
const result = await artifactManager.findArtifactContent(agentId, filename, query, startLine);
|
|
23
|
+
return buildFindContentResult(filename, query, result, limit);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return getErrorToolResult(error, "Failed to search artifact content.");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const config = {
|
|
30
|
+
name: FIND_CONTENT_IN_ARTIFACT_TOOL_NAME,
|
|
31
|
+
description: "Search a TEXT artifact you own for a substring and return matching lines with their 1-based line numbers. Case-insensitive. Use startLine to skip ahead and limit to cap the number of matches.",
|
|
32
|
+
inputSchema,
|
|
33
|
+
handler,
|
|
34
|
+
};
|
|
35
|
+
return config;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=find-content-in-artifact.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getErrorToolResult, textToolResult } from "../tool-utils.js";
|
|
3
|
+
import { buildFindContentResult } from "./artifacts-mcp-server-utils.js";
|
|
4
|
+
export const FIND_CONTENT_IN_CIRCLE_ARTIFACT_TOOL_NAME = "find_content_in_circle_artifact";
|
|
5
|
+
export function getFindContentInCircleArtifactToolConfig(agentId, artifactManager) {
|
|
6
|
+
const inputSchema = {
|
|
7
|
+
circle_id: z.string().describe("The circle ID that owns the artifact."),
|
|
8
|
+
filename: z.string().describe("Name of an existing TEXT circle artifact to search."),
|
|
9
|
+
query: z.string().min(1).describe("Substring to search for. Case-insensitive."),
|
|
10
|
+
startLine: z
|
|
11
|
+
.number()
|
|
12
|
+
.min(1)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Optional. 1-based line number to start searching from. Defaults to 1."),
|
|
15
|
+
limit: z.number().min(1).optional().describe("Optional. Maximum number of matches to return."),
|
|
16
|
+
};
|
|
17
|
+
const handler = async ({ circle_id, filename: rawFilename, query, startLine, limit, }) => {
|
|
18
|
+
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
19
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
20
|
+
}
|
|
21
|
+
const filename = rawFilename.trim();
|
|
22
|
+
try {
|
|
23
|
+
if (!query) {
|
|
24
|
+
return textToolResult(["Search query must not be empty."], true);
|
|
25
|
+
}
|
|
26
|
+
const result = await artifactManager.findCircleArtifactContent(circle_id, filename, query, startLine);
|
|
27
|
+
return buildFindContentResult(filename, query, result, limit);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return getErrorToolResult(error, "Failed to search circle artifact content.");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const config = {
|
|
34
|
+
name: FIND_CONTENT_IN_CIRCLE_ARTIFACT_TOOL_NAME,
|
|
35
|
+
description: "Search a TEXT circle artifact for a substring and return matching lines with their 1-based line numbers. Only direct members of the circle can search. Case-insensitive. Use startLine to skip ahead and limit to cap the number of matches.",
|
|
36
|
+
inputSchema,
|
|
37
|
+
handler,
|
|
38
|
+
};
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=find-content-in-circle-artifact.js.map
|
|
@@ -11,29 +11,40 @@ export function getListArtifactsToolConfig(agentId, artifactManager, registry, c
|
|
|
11
11
|
.enum(ARTIFACT_TYPE_VALUES)
|
|
12
12
|
.optional()
|
|
13
13
|
.describe(`Filter by artifact type. Values: ${ARTIFACT_TYPE_VALUES.join(", ")}`),
|
|
14
|
+
tags: z
|
|
15
|
+
.array(z.string())
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter by tags. Only artifacts that have every specified tag are returned."),
|
|
14
18
|
limit: z.number().optional().describe("Number of artifacts to return per page."),
|
|
15
19
|
skip: z.number().optional().describe("Number of artifacts to skip for pagination."),
|
|
16
20
|
};
|
|
17
|
-
const handler = async ({ agent_id, type, limit, skip }) => {
|
|
21
|
+
const handler = async ({ agent_id, type, tags, limit, skip }) => {
|
|
18
22
|
const targetId = agent_id ?? agentId;
|
|
19
23
|
if (agent_id) {
|
|
20
24
|
try {
|
|
21
25
|
registry.getAgent(agent_id);
|
|
22
26
|
}
|
|
23
27
|
catch {
|
|
24
|
-
return textToolResult(["
|
|
28
|
+
return textToolResult(["Target agent not found."], true);
|
|
25
29
|
}
|
|
26
30
|
if (!circleManager.isAgentVisible(agentId, targetId)) {
|
|
27
|
-
return textToolResult(["
|
|
31
|
+
return textToolResult(["Target agent is not visible to you."], true);
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
try {
|
|
31
35
|
const [artifacts, userTimezone] = await Promise.all([
|
|
32
|
-
artifactManager.listArtifacts(targetId, { type }),
|
|
36
|
+
artifactManager.listArtifacts(targetId, { type, tags }),
|
|
33
37
|
sensorManager.getUserTimezone(),
|
|
34
38
|
]);
|
|
35
39
|
if (artifacts.length === 0) {
|
|
36
|
-
const
|
|
40
|
+
const filterParts = [];
|
|
41
|
+
if (type) {
|
|
42
|
+
filterParts.push(`type ${type}`);
|
|
43
|
+
}
|
|
44
|
+
if (tags?.length) {
|
|
45
|
+
filterParts.push(`tags [${tags.join(", ")}]`);
|
|
46
|
+
}
|
|
47
|
+
const suffix = filterParts.length ? ` matching ${filterParts.join(" and ")}` : "";
|
|
37
48
|
return textToolResult([`No artifacts found for agent ${targetId}${suffix}.`]);
|
|
38
49
|
}
|
|
39
50
|
const pagination = applyPagination(artifacts, limit || DEFAULT_ARTIFACTS_LIMIT, skip);
|
|
@@ -11,20 +11,31 @@ export function getListCircleArtifactsToolConfig(agentId, artifactManager, senso
|
|
|
11
11
|
.enum(ARTIFACT_TYPE_VALUES)
|
|
12
12
|
.optional()
|
|
13
13
|
.describe(`Filter by artifact type. Values: ${ARTIFACT_TYPE_VALUES.join(", ")}`),
|
|
14
|
+
tags: z
|
|
15
|
+
.array(z.string())
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter by tags. Only artifacts that have every specified tag are returned."),
|
|
14
18
|
limit: z.number().optional().describe("Number of artifacts to return per page."),
|
|
15
19
|
skip: z.number().optional().describe("Number of artifacts to skip for pagination."),
|
|
16
20
|
};
|
|
17
|
-
const handler = async ({ circle_id, type, limit, skip }) => {
|
|
21
|
+
const handler = async ({ circle_id, type, tags, limit, skip }) => {
|
|
18
22
|
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
19
|
-
return textToolResult(["
|
|
23
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
20
24
|
}
|
|
21
25
|
try {
|
|
22
26
|
const [artifacts, userTimezone] = await Promise.all([
|
|
23
|
-
artifactManager.listCircleArtifacts(circle_id, { type }),
|
|
27
|
+
artifactManager.listCircleArtifacts(circle_id, { type, tags }),
|
|
24
28
|
sensorManager.getUserTimezone(),
|
|
25
29
|
]);
|
|
26
30
|
if (artifacts.length === 0) {
|
|
27
|
-
const
|
|
31
|
+
const filterParts = [];
|
|
32
|
+
if (type) {
|
|
33
|
+
filterParts.push(`type ${type}`);
|
|
34
|
+
}
|
|
35
|
+
if (tags?.length) {
|
|
36
|
+
filterParts.push(`tags [${tags.join(", ")}]`);
|
|
37
|
+
}
|
|
38
|
+
const suffix = filterParts.length ? ` matching ${filterParts.join(" and ")}` : "";
|
|
28
39
|
return textToolResult([`No artifacts found for circle ${circle_id}${suffix}.`]);
|
|
29
40
|
}
|
|
30
41
|
const pagination = applyPagination(artifacts, limit || DEFAULT_CIRCLE_ARTIFACTS_LIMIT, skip);
|
|
@@ -23,15 +23,14 @@ export function getReadArtifactToolConfig(agentId, artifactManager, registry, ci
|
|
|
23
23
|
registry.getAgent(agent_id);
|
|
24
24
|
}
|
|
25
25
|
catch {
|
|
26
|
-
return textToolResult(["
|
|
26
|
+
return textToolResult(["Target agent not found."], true);
|
|
27
27
|
}
|
|
28
28
|
if (!circleManager.isAgentVisible(agentId, agent_id)) {
|
|
29
|
-
return textToolResult(["
|
|
29
|
+
return textToolResult(["Target agent is not visible to you."], true);
|
|
30
30
|
}
|
|
31
31
|
try {
|
|
32
|
-
const [content, metadata, userTimezone] = await Promise.all([
|
|
32
|
+
const [{ content, metadata }, userTimezone] = await Promise.all([
|
|
33
33
|
artifactManager.readArtifact(agent_id, filename, { useAdapter: true }),
|
|
34
|
-
artifactManager.getArtifactMetadata(agent_id, filename),
|
|
35
34
|
sensorManager.getUserTimezone(),
|
|
36
35
|
]);
|
|
37
36
|
return buildReadArtifactResult(content, metadata, userTimezone, { showLineNumber, startLine, limit });
|
|
@@ -20,12 +20,11 @@ export function getReadCircleArtifactToolConfig(agentId, artifactManager, sensor
|
|
|
20
20
|
};
|
|
21
21
|
const handler = async ({ circle_id, filename, showLineNumber, startLine, limit, }) => {
|
|
22
22
|
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
23
|
-
return textToolResult(["
|
|
23
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
const [content, metadata, userTimezone] = await Promise.all([
|
|
26
|
+
const [{ content, metadata }, userTimezone] = await Promise.all([
|
|
27
27
|
artifactManager.readCircleArtifact(circle_id, filename, { useAdapter: true }),
|
|
28
|
-
artifactManager.getCircleArtifactMetadata(circle_id, filename),
|
|
29
28
|
sensorManager.getUserTimezone(),
|
|
30
29
|
]);
|
|
31
30
|
return buildReadArtifactResult(content, metadata, userTimezone, { showLineNumber, startLine, limit });
|
|
@@ -6,7 +6,7 @@ import { ARTIFACT_CONTENT_TYPE_VALUES, ARTIFACT_TYPE_VALUES } from "./artifacts-
|
|
|
6
6
|
export const WRITE_ARTIFACT_TOOL_NAME = "write_artifact";
|
|
7
7
|
export function getWriteArtifactToolConfig(agentId, artifactManager, sensorManager) {
|
|
8
8
|
const inputSchema = {
|
|
9
|
-
filename: z.string().describe("Name of the file to
|
|
9
|
+
filename: z.string().describe("Name of the file to write, e.g. 'report.md' or 'data.json'."),
|
|
10
10
|
content: z
|
|
11
11
|
.string()
|
|
12
12
|
.describe("The content to write. For TEXT content type, provide the raw text. For binary content types (IMAGE, AUDIO, BINARY), provide base64-encoded data."),
|
|
@@ -17,9 +17,13 @@ export function getWriteArtifactToolConfig(agentId, artifactManager, sensorManag
|
|
|
17
17
|
content_type: z
|
|
18
18
|
.enum(ARTIFACT_CONTENT_TYPE_VALUES)
|
|
19
19
|
.optional()
|
|
20
|
-
.describe(`Content type annotation. Values: ${ARTIFACT_CONTENT_TYPE_VALUES.join(", ")}. Defaults to ${ARTIFACT_CONTENT_TYPE.TEXT}
|
|
20
|
+
.describe(`Content type annotation. Values: ${ARTIFACT_CONTENT_TYPE_VALUES.join(", ")}. Defaults to ${ARTIFACT_CONTENT_TYPE.TEXT}.`),
|
|
21
|
+
tags: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Tags to attach to the artifact. Fully replaces existing tags; omit to leave the artifact untagged."),
|
|
21
25
|
};
|
|
22
|
-
const handler = async ({ filename: rawFilename, content, type, content_type }) => {
|
|
26
|
+
const handler = async ({ filename: rawFilename, content, type, content_type, tags, }) => {
|
|
23
27
|
const filename = rawFilename.trim();
|
|
24
28
|
try {
|
|
25
29
|
const isBinary = content_type && content_type !== ARTIFACT_CONTENT_TYPE.TEXT;
|
|
@@ -27,6 +31,7 @@ export function getWriteArtifactToolConfig(agentId, artifactManager, sensorManag
|
|
|
27
31
|
const metadata = await artifactManager.writeArtifact(agentId, filename, artifactContent, {
|
|
28
32
|
type,
|
|
29
33
|
contentType: content_type,
|
|
34
|
+
tags,
|
|
30
35
|
createdBy: { sourceType: AGENT_TASK_SOURCE_TYPE.AGENT, agentId },
|
|
31
36
|
});
|
|
32
37
|
const userTimezone = await sensorManager.getUserTimezone();
|
|
@@ -43,7 +48,7 @@ export function getWriteArtifactToolConfig(agentId, artifactManager, sensorManag
|
|
|
43
48
|
};
|
|
44
49
|
const config = {
|
|
45
50
|
name: WRITE_ARTIFACT_TOOL_NAME,
|
|
46
|
-
description: "Save a file to your own artifacts folder. Other agents can read your artifacts to collaborate.
|
|
51
|
+
description: "Save a file to your own artifacts folder, creating it or replacing the existing file at that name. Other agents can read your artifacts to collaborate. Use edit_artifact for surgical line-level changes to a TEXT artifact.",
|
|
47
52
|
inputSchema,
|
|
48
53
|
handler,
|
|
49
54
|
};
|
|
@@ -6,8 +6,8 @@ import { ARTIFACT_CONTENT_TYPE_VALUES, ARTIFACT_TYPE_VALUES } from "./artifacts-
|
|
|
6
6
|
export const WRITE_CIRCLE_ARTIFACT_TOOL_NAME = "write_circle_artifact";
|
|
7
7
|
export function getWriteCircleArtifactToolConfig(agentId, artifactManager, sensorManager) {
|
|
8
8
|
const inputSchema = {
|
|
9
|
-
circle_id: z.string().describe("The circle ID to write the artifact to"),
|
|
10
|
-
filename: z.string().describe("Name of the file to
|
|
9
|
+
circle_id: z.string().describe("The circle ID to write the artifact to."),
|
|
10
|
+
filename: z.string().describe("Name of the file to write."),
|
|
11
11
|
content: z
|
|
12
12
|
.string()
|
|
13
13
|
.describe("The content to write. For TEXT content type, provide the raw text. For binary content types (IMAGE, AUDIO, BINARY), provide base64-encoded data."),
|
|
@@ -18,11 +18,15 @@ export function getWriteCircleArtifactToolConfig(agentId, artifactManager, senso
|
|
|
18
18
|
content_type: z
|
|
19
19
|
.enum(ARTIFACT_CONTENT_TYPE_VALUES)
|
|
20
20
|
.optional()
|
|
21
|
-
.describe(`Content type annotation. Values: ${ARTIFACT_CONTENT_TYPE_VALUES.join(", ")}. Defaults to ${ARTIFACT_CONTENT_TYPE.TEXT}
|
|
21
|
+
.describe(`Content type annotation. Values: ${ARTIFACT_CONTENT_TYPE_VALUES.join(", ")}. Defaults to ${ARTIFACT_CONTENT_TYPE.TEXT}.`),
|
|
22
|
+
tags: z
|
|
23
|
+
.array(z.string())
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Tags to attach to the artifact. Fully replaces existing tags; omit to leave the artifact untagged."),
|
|
22
26
|
};
|
|
23
|
-
const handler = async ({ circle_id, filename: rawFilename, content, type, content_type, }) => {
|
|
27
|
+
const handler = async ({ circle_id, filename: rawFilename, content, type, content_type, tags, }) => {
|
|
24
28
|
if (!artifactManager.isDirectCircleMember(circle_id, agentId)) {
|
|
25
|
-
return textToolResult(["
|
|
29
|
+
return textToolResult(["You are not a direct member of this circle."], true);
|
|
26
30
|
}
|
|
27
31
|
const filename = rawFilename.trim();
|
|
28
32
|
try {
|
|
@@ -31,6 +35,7 @@ export function getWriteCircleArtifactToolConfig(agentId, artifactManager, senso
|
|
|
31
35
|
const metadata = await artifactManager.writeCircleArtifact(circle_id, filename, artifactContent, {
|
|
32
36
|
type,
|
|
33
37
|
contentType: content_type,
|
|
38
|
+
tags,
|
|
34
39
|
createdBy: { sourceType: AGENT_TASK_SOURCE_TYPE.AGENT, agentId },
|
|
35
40
|
});
|
|
36
41
|
const userTimezone = await sensorManager.getUserTimezone();
|
|
@@ -47,7 +52,7 @@ export function getWriteCircleArtifactToolConfig(agentId, artifactManager, senso
|
|
|
47
52
|
};
|
|
48
53
|
const config = {
|
|
49
54
|
name: WRITE_CIRCLE_ARTIFACT_TOOL_NAME,
|
|
50
|
-
description: "Save a file to a circle's shared artifacts folder. Only direct members of the circle can read and write circle artifacts.",
|
|
55
|
+
description: "Save a file to a circle's shared artifacts folder, creating it or replacing the existing file at that name. Only direct members of the circle can read and write circle artifacts. Use edit_circle_artifact for surgical line-level changes to a TEXT artifact.",
|
|
51
56
|
inputSchema,
|
|
52
57
|
handler,
|
|
53
58
|
};
|
package/dist/mcp/tool-utils.js
CHANGED
|
@@ -76,7 +76,7 @@ export function processTextContent(text, options) {
|
|
|
76
76
|
const clampedEnd = Math.min(options.limit !== undefined ? start + options.limit : totalLines, totalLines);
|
|
77
77
|
const sliced = allLines.slice(start, clampedEnd);
|
|
78
78
|
if (options.showLineNumber) {
|
|
79
|
-
headerParts.push("Lines are prefixed with [LNNN] markers. These markers are NOT part of the
|
|
79
|
+
headerParts.push("Lines are prefixed with [LNNN] markers. These markers are NOT part of the line content.");
|
|
80
80
|
}
|
|
81
81
|
const hasRange = options.startLine !== undefined || options.limit !== undefined;
|
|
82
82
|
headerParts.push(`--- CONTENT${hasRange ? ` (lines ${start + 1} - ${clampedEnd})` : ""} ---`);
|