@schoolai/shipyard-mcp 0.4.1 → 0.4.2
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/apps/hook/dist/index.cjs +110 -63
- package/apps/server/dist/{chunk-6EWQC5VA.js → chunk-6TW62VVK.js} +326 -98
- package/apps/server/dist/{chunk-HFZCBGQ3.js → chunk-DLDKEWOB.js} +43 -11
- package/apps/server/dist/index.js +435 -54
- package/apps/server/dist/{input-request-manager-QT4IVCLS.js → input-request-manager-FK4REBEZ.js} +2 -2
- package/apps/server/dist/{session-registry-CBDXMXY3.js → session-registry-RL3D6JZU.js} +1 -1
- package/package.json +1 -1
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
GitHubAuthError,
|
|
4
4
|
GitHubPRResponseSchema,
|
|
5
|
+
OriginPlatformValues,
|
|
5
6
|
PLAN_INDEX_DOC_NAME,
|
|
6
7
|
PlanStatusValues,
|
|
7
8
|
TOOL_NAMES,
|
|
9
|
+
ThreadCommentSchema,
|
|
8
10
|
YDOC_KEYS,
|
|
9
11
|
addArtifact,
|
|
10
12
|
addDeliverable,
|
|
@@ -15,19 +17,24 @@ import {
|
|
|
15
17
|
createPlanUrlWithHistory,
|
|
16
18
|
createPlanWebUrl,
|
|
17
19
|
createUserResolver,
|
|
20
|
+
detectPlatform,
|
|
18
21
|
extractDeliverables,
|
|
19
22
|
extractTextFromCommentBody,
|
|
20
23
|
formatDeliverablesForLLM,
|
|
21
24
|
formatDiffCommentsForLLM,
|
|
22
25
|
generateSessionToken,
|
|
23
26
|
getArtifacts,
|
|
27
|
+
getClientInfo,
|
|
24
28
|
getDeliverables,
|
|
29
|
+
getDisplayName,
|
|
25
30
|
getGitHubUsername,
|
|
26
31
|
getLinkedPRs,
|
|
27
32
|
getLocalChanges,
|
|
33
|
+
getLocalDiffCommentById,
|
|
28
34
|
getLocalDiffComments,
|
|
29
35
|
getOctokit,
|
|
30
36
|
getOrCreateDoc,
|
|
37
|
+
getPRReviewCommentById,
|
|
31
38
|
getPRReviewComments,
|
|
32
39
|
getPlanEvents,
|
|
33
40
|
getPlanMetadata,
|
|
@@ -46,23 +53,29 @@ import {
|
|
|
46
53
|
linkArtifactToDeliverable,
|
|
47
54
|
linkPR,
|
|
48
55
|
logPlanEvent,
|
|
56
|
+
parseCommentId,
|
|
49
57
|
parseRepoString,
|
|
58
|
+
parseThreadId,
|
|
50
59
|
parseThreads,
|
|
51
|
-
registryConfig,
|
|
52
60
|
releaseHubLock,
|
|
61
|
+
replyToLocalDiffComment,
|
|
62
|
+
replyToPRReviewComment,
|
|
53
63
|
resetPlanToDraft,
|
|
64
|
+
setClientInfo,
|
|
54
65
|
setPlanIndexEntry,
|
|
55
66
|
setPlanMetadata,
|
|
56
67
|
startRegistryServer,
|
|
68
|
+
toPlainObject,
|
|
57
69
|
touchPlanIndexEntry,
|
|
58
70
|
transitionPlanStatus,
|
|
59
71
|
tryAcquireHubLock,
|
|
60
72
|
uploadArtifact,
|
|
61
73
|
webConfig
|
|
62
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-6TW62VVK.js";
|
|
63
75
|
import {
|
|
64
|
-
logger
|
|
65
|
-
|
|
76
|
+
logger,
|
|
77
|
+
registryConfig
|
|
78
|
+
} from "./chunk-DLDKEWOB.js";
|
|
66
79
|
|
|
67
80
|
// src/index.ts
|
|
68
81
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -81,7 +94,7 @@ import * as os from "os";
|
|
|
81
94
|
import * as path from "path";
|
|
82
95
|
import * as vm from "vm";
|
|
83
96
|
import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
|
|
84
|
-
import { z as
|
|
97
|
+
import { z as z15 } from "zod";
|
|
85
98
|
|
|
86
99
|
// src/tools/add-artifact.ts
|
|
87
100
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
@@ -110,7 +123,6 @@ import { nanoid } from "nanoid";
|
|
|
110
123
|
|
|
111
124
|
// src/local-artifacts.ts
|
|
112
125
|
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
113
|
-
import { homedir } from "os";
|
|
114
126
|
import { join, resolve, sep } from "path";
|
|
115
127
|
async function storeLocalArtifact(planId, filename, buffer) {
|
|
116
128
|
const planDir = join(ARTIFACTS_DIR, planId);
|
|
@@ -138,7 +150,7 @@ async function deleteLocalArtifact(artifactId) {
|
|
|
138
150
|
return false;
|
|
139
151
|
}
|
|
140
152
|
}
|
|
141
|
-
var ARTIFACTS_DIR = join(
|
|
153
|
+
var ARTIFACTS_DIR = join(registryConfig.SHIPYARD_STATE_DIR, "artifacts");
|
|
142
154
|
|
|
143
155
|
// src/tools/artifact-helpers.ts
|
|
144
156
|
async function resolveArtifactContent(input) {
|
|
@@ -706,10 +718,9 @@ async function handleAutoComplete(doc, metadata, deliverables, actorName, taskId
|
|
|
706
718
|
actorName,
|
|
707
719
|
snapshotMessage: "Task completed - all deliverables fulfilled"
|
|
708
720
|
});
|
|
709
|
-
const { homedir: homedir2 } = await import("os");
|
|
710
721
|
const { join: join3 } = await import("path");
|
|
711
722
|
const { mkdir: mkdir2 } = await import("fs/promises");
|
|
712
|
-
const snapshotsDir = join3(
|
|
723
|
+
const snapshotsDir = join3(registryConfig.SHIPYARD_STATE_DIR, "snapshots");
|
|
713
724
|
await mkdir2(snapshotsDir, { recursive: true });
|
|
714
725
|
const snapshotFile = join3(snapshotsDir, `${taskId}.txt`);
|
|
715
726
|
await writeFile2(snapshotFile, result.snapshotUrl, "utf-8");
|
|
@@ -958,7 +969,7 @@ import { ServerBlockNoteEditor as ServerBlockNoteEditor3 } from "@blocknote/serv
|
|
|
958
969
|
import { nanoid as nanoid2 } from "nanoid";
|
|
959
970
|
import open from "open";
|
|
960
971
|
import { z as z4 } from "zod";
|
|
961
|
-
var OriginPlatformEnum = z4.enum(
|
|
972
|
+
var OriginPlatformEnum = z4.enum(OriginPlatformValues);
|
|
962
973
|
var CreateTaskInput = z4.object({
|
|
963
974
|
title: z4.string().describe("Task title"),
|
|
964
975
|
content: z4.string().describe("Task content (markdown)"),
|
|
@@ -986,8 +997,15 @@ function buildOriginMetadata(platform, sessionId, metadata) {
|
|
|
986
997
|
generationId: typeof generationId === "string" ? generationId : void 0
|
|
987
998
|
};
|
|
988
999
|
}
|
|
989
|
-
case "windsurf":
|
|
990
1000
|
case "aider":
|
|
1001
|
+
case "browser":
|
|
1002
|
+
case "claude-code":
|
|
1003
|
+
case "cline":
|
|
1004
|
+
case "codex":
|
|
1005
|
+
case "continue":
|
|
1006
|
+
case "vscode":
|
|
1007
|
+
case "windsurf":
|
|
1008
|
+
case "zed":
|
|
991
1009
|
case "unknown":
|
|
992
1010
|
return { platform: "unknown", cwd: process.cwd() };
|
|
993
1011
|
default: {
|
|
@@ -1064,7 +1082,7 @@ Bad deliverables (not provable):
|
|
|
1064
1082
|
prNumber: { type: "number", description: "PR number. Required for artifact uploads." },
|
|
1065
1083
|
originPlatform: {
|
|
1066
1084
|
type: "string",
|
|
1067
|
-
enum: [
|
|
1085
|
+
enum: [...OriginPlatformValues],
|
|
1068
1086
|
description: "Platform where this plan originated. Used for conversation export/import."
|
|
1069
1087
|
},
|
|
1070
1088
|
originSessionId: {
|
|
@@ -1688,11 +1706,10 @@ function formatThread(thread, number, selectedTextMaxLength, resolveUser) {
|
|
|
1688
1706
|
const bodyText = extractTextFromCommentBody(comment.body);
|
|
1689
1707
|
const authorName = resolveUser ? resolveUser(comment.userId) : comment.userId.slice(0, 8);
|
|
1690
1708
|
if (idx === 0) {
|
|
1691
|
-
output +=
|
|
1709
|
+
output += `[thread:${thread.id}] **${authorName}:** ${bodyText}
|
|
1692
1710
|
`;
|
|
1693
1711
|
} else {
|
|
1694
|
-
output +=
|
|
1695
|
-
> **${authorName} (Reply):** ${bodyText}
|
|
1712
|
+
output += `[comment:${comment.id}] **${authorName} (Reply):** ${bodyText}
|
|
1696
1713
|
`;
|
|
1697
1714
|
}
|
|
1698
1715
|
});
|
|
@@ -2076,11 +2093,307 @@ Use this token for add_artifact, read_task, link_pr, and other task operations.`
|
|
|
2076
2093
|
}
|
|
2077
2094
|
};
|
|
2078
2095
|
|
|
2079
|
-
// src/tools/
|
|
2096
|
+
// src/tools/reply-to-diff-comment.ts
|
|
2080
2097
|
import { z as z10 } from "zod";
|
|
2081
|
-
var
|
|
2082
|
-
taskId: z10.string().describe("
|
|
2083
|
-
|
|
2098
|
+
var ReplyToDiffCommentInput = z10.object({
|
|
2099
|
+
taskId: z10.string().describe("Plan ID"),
|
|
2100
|
+
sessionToken: z10.string().describe("Session token from create_plan"),
|
|
2101
|
+
commentId: z10.string().describe("Comment ID (PR or local diff comment)"),
|
|
2102
|
+
body: z10.string().describe("Reply text")
|
|
2103
|
+
});
|
|
2104
|
+
var replyToDiffCommentTool = {
|
|
2105
|
+
definition: {
|
|
2106
|
+
name: TOOL_NAMES.REPLY_TO_DIFF_COMMENT,
|
|
2107
|
+
description: `Reply to a PR review comment or local diff comment.
|
|
2108
|
+
|
|
2109
|
+
Allows AI to respond to code review feedback.
|
|
2110
|
+
Replies appear inline in the Changes tab with the original comment.
|
|
2111
|
+
|
|
2112
|
+
USAGE:
|
|
2113
|
+
- commentId: Comment ID from readDiffComments output (format: [pr:abc123] or [local:abc123])
|
|
2114
|
+
- body: Reply text (markdown supported)
|
|
2115
|
+
|
|
2116
|
+
The tool automatically detects whether the comment is a PR or local diff comment.
|
|
2117
|
+
|
|
2118
|
+
EXAMPLE:
|
|
2119
|
+
reply_to_diff_comment({
|
|
2120
|
+
taskId: "abc123",
|
|
2121
|
+
sessionToken: "token",
|
|
2122
|
+
commentId: "pr:xyz789",
|
|
2123
|
+
body: "Good point! I'll add that validation in the next commit."
|
|
2124
|
+
})`,
|
|
2125
|
+
inputSchema: {
|
|
2126
|
+
type: "object",
|
|
2127
|
+
properties: {
|
|
2128
|
+
taskId: { type: "string", description: "Plan ID" },
|
|
2129
|
+
sessionToken: { type: "string", description: "Session token from create_plan" },
|
|
2130
|
+
commentId: { type: "string", description: "Comment ID (PR or local)" },
|
|
2131
|
+
body: { type: "string", description: "Reply text" }
|
|
2132
|
+
},
|
|
2133
|
+
required: ["taskId", "sessionToken", "commentId", "body"]
|
|
2134
|
+
}
|
|
2135
|
+
},
|
|
2136
|
+
/**
|
|
2137
|
+
* Complexity of 20 is acceptable here because the function handles:
|
|
2138
|
+
* - Input validation and session verification
|
|
2139
|
+
* - Comment ID parsing with type detection
|
|
2140
|
+
* - Two distinct comment types (PR vs local) with separate error handling
|
|
2141
|
+
* - Success/error responses for each code path
|
|
2142
|
+
* Extracting helpers would fragment the error handling context.
|
|
2143
|
+
*/
|
|
2144
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Tool handler needs unified error handling for two comment types
|
|
2145
|
+
handler: async (args) => {
|
|
2146
|
+
const input = ReplyToDiffCommentInput.parse(args);
|
|
2147
|
+
logger.info({ taskId: input.taskId, commentId: input.commentId }, "Replying to diff comment");
|
|
2148
|
+
const ydoc = await getOrCreateDoc(input.taskId);
|
|
2149
|
+
const metadata = getPlanMetadata(ydoc);
|
|
2150
|
+
if (!metadata) {
|
|
2151
|
+
return {
|
|
2152
|
+
content: [{ type: "text", text: `Plan "${input.taskId}" not found.` }],
|
|
2153
|
+
isError: true
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
if (!metadata.sessionTokenHash || !verifySessionToken(input.sessionToken, metadata.sessionTokenHash)) {
|
|
2157
|
+
return {
|
|
2158
|
+
content: [{ type: "text", text: `Invalid session token for plan "${input.taskId}".` }],
|
|
2159
|
+
isError: true
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
const actorName = await getGitHubUsername();
|
|
2163
|
+
const clientInfoName = getClientInfo();
|
|
2164
|
+
const { platform } = detectPlatform(clientInfoName);
|
|
2165
|
+
const agentDisplayName = getDisplayName(platform, actorName);
|
|
2166
|
+
const parsed = parseCommentId(input.commentId);
|
|
2167
|
+
logger.debug(
|
|
2168
|
+
{ inputCommentId: input.commentId, parsedType: parsed.type, parsedId: parsed.id },
|
|
2169
|
+
"Parsed comment ID from input"
|
|
2170
|
+
);
|
|
2171
|
+
const prComment = parsed.type === "pr" || parsed.type === "unknown" ? getPRReviewCommentById(ydoc, parsed.id) : null;
|
|
2172
|
+
if (prComment) {
|
|
2173
|
+
try {
|
|
2174
|
+
const reply = replyToPRReviewComment(
|
|
2175
|
+
ydoc,
|
|
2176
|
+
parsed.id,
|
|
2177
|
+
input.body,
|
|
2178
|
+
agentDisplayName,
|
|
2179
|
+
actorName
|
|
2180
|
+
);
|
|
2181
|
+
logPlanEvent(ydoc, "comment_added", actorName, {
|
|
2182
|
+
commentId: reply.id,
|
|
2183
|
+
prNumber: prComment.prNumber
|
|
2184
|
+
});
|
|
2185
|
+
logger.info(
|
|
2186
|
+
{ taskId: input.taskId, commentId: reply.id, parentCommentId: parsed.id },
|
|
2187
|
+
"PR review comment reply added"
|
|
2188
|
+
);
|
|
2189
|
+
return {
|
|
2190
|
+
content: [
|
|
2191
|
+
{
|
|
2192
|
+
type: "text",
|
|
2193
|
+
text: `Reply added to PR review comment!
|
|
2194
|
+
|
|
2195
|
+
Comment ID: ${reply.id}
|
|
2196
|
+
Parent Comment ID: ${parsed.id}
|
|
2197
|
+
PR: #${prComment.prNumber}
|
|
2198
|
+
File: ${prComment.path}:${prComment.line}
|
|
2199
|
+
|
|
2200
|
+
The reply will appear in the Changes tab inline with the original comment.`
|
|
2201
|
+
}
|
|
2202
|
+
]
|
|
2203
|
+
};
|
|
2204
|
+
} catch (error) {
|
|
2205
|
+
logger.error({ error, commentId: parsed.id }, "Failed to reply to PR comment");
|
|
2206
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2207
|
+
return {
|
|
2208
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
2209
|
+
isError: true
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
const localComment = parsed.type === "local" || parsed.type === "unknown" ? getLocalDiffCommentById(ydoc, parsed.id) : null;
|
|
2214
|
+
if (localComment) {
|
|
2215
|
+
try {
|
|
2216
|
+
const reply = replyToLocalDiffComment(
|
|
2217
|
+
ydoc,
|
|
2218
|
+
parsed.id,
|
|
2219
|
+
input.body,
|
|
2220
|
+
agentDisplayName,
|
|
2221
|
+
actorName
|
|
2222
|
+
);
|
|
2223
|
+
logPlanEvent(ydoc, "comment_added", actorName, {
|
|
2224
|
+
commentId: reply.id
|
|
2225
|
+
});
|
|
2226
|
+
logger.info(
|
|
2227
|
+
{ taskId: input.taskId, commentId: reply.id, parentCommentId: parsed.id },
|
|
2228
|
+
"Local diff comment reply added"
|
|
2229
|
+
);
|
|
2230
|
+
return {
|
|
2231
|
+
content: [
|
|
2232
|
+
{
|
|
2233
|
+
type: "text",
|
|
2234
|
+
text: `Reply added to local diff comment!
|
|
2235
|
+
|
|
2236
|
+
Comment ID: ${reply.id}
|
|
2237
|
+
Parent Comment ID: ${parsed.id}
|
|
2238
|
+
File: ${localComment.path}:${localComment.line}
|
|
2239
|
+
|
|
2240
|
+
The reply will appear in the Changes tab inline with the original comment.`
|
|
2241
|
+
}
|
|
2242
|
+
]
|
|
2243
|
+
};
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
logger.error({ error, commentId: parsed.id }, "Failed to reply to local comment");
|
|
2246
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2247
|
+
return {
|
|
2248
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
2249
|
+
isError: true
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
return {
|
|
2254
|
+
content: [
|
|
2255
|
+
{
|
|
2256
|
+
type: "text",
|
|
2257
|
+
text: `Comment "${parsed.id}" not found in plan "${input.taskId}".${input.commentId !== parsed.id ? ` (parsed from input: "${input.commentId}")` : ""} Make sure the comment ID is correct and the comment exists.`
|
|
2258
|
+
}
|
|
2259
|
+
],
|
|
2260
|
+
isError: true
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
|
|
2265
|
+
// src/tools/reply-to-thread-comment.ts
|
|
2266
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
2267
|
+
import { z as z11 } from "zod";
|
|
2268
|
+
var ReplyToThreadCommentInput = z11.object({
|
|
2269
|
+
taskId: z11.string().describe("Plan ID"),
|
|
2270
|
+
sessionToken: z11.string().describe("Session token from create_plan"),
|
|
2271
|
+
threadId: z11.string().describe("Thread ID to reply to"),
|
|
2272
|
+
body: z11.string().describe("Reply text")
|
|
2273
|
+
});
|
|
2274
|
+
var replyToThreadCommentTool = {
|
|
2275
|
+
definition: {
|
|
2276
|
+
name: TOOL_NAMES.REPLY_TO_THREAD_COMMENT,
|
|
2277
|
+
description: `Reply to a BlockNote inline thread comment.
|
|
2278
|
+
|
|
2279
|
+
Allows AI to respond to reviewer feedback on specific blocks.
|
|
2280
|
+
Replies appear in the comment thread sidebar.
|
|
2281
|
+
|
|
2282
|
+
USAGE:
|
|
2283
|
+
- threadId: Thread ID from read_plan output (format: [thread:abc123])
|
|
2284
|
+
- body: Reply text (plain text or BlockNote structured content)
|
|
2285
|
+
|
|
2286
|
+
EXAMPLE:
|
|
2287
|
+
reply_to_thread_comment({
|
|
2288
|
+
taskId: "abc123",
|
|
2289
|
+
sessionToken: "token",
|
|
2290
|
+
threadId: "thread-xyz",
|
|
2291
|
+
body: "Good point! I'll add that validation."
|
|
2292
|
+
})`,
|
|
2293
|
+
inputSchema: {
|
|
2294
|
+
type: "object",
|
|
2295
|
+
properties: {
|
|
2296
|
+
taskId: { type: "string", description: "Plan ID" },
|
|
2297
|
+
sessionToken: { type: "string", description: "Session token from create_plan" },
|
|
2298
|
+
threadId: { type: "string", description: "Thread ID to reply to" },
|
|
2299
|
+
body: { type: "string", description: "Reply text" }
|
|
2300
|
+
},
|
|
2301
|
+
required: ["taskId", "sessionToken", "threadId", "body"]
|
|
2302
|
+
}
|
|
2303
|
+
},
|
|
2304
|
+
handler: async (args) => {
|
|
2305
|
+
const input = ReplyToThreadCommentInput.parse(args);
|
|
2306
|
+
logger.info({ taskId: input.taskId, threadId: input.threadId }, "Replying to thread comment");
|
|
2307
|
+
const ydoc = await getOrCreateDoc(input.taskId);
|
|
2308
|
+
const metadata = getPlanMetadata(ydoc);
|
|
2309
|
+
if (!metadata) {
|
|
2310
|
+
return {
|
|
2311
|
+
content: [{ type: "text", text: `Plan "${input.taskId}" not found.` }],
|
|
2312
|
+
isError: true
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
if (!metadata.sessionTokenHash || !verifySessionToken(input.sessionToken, metadata.sessionTokenHash)) {
|
|
2316
|
+
return {
|
|
2317
|
+
content: [{ type: "text", text: `Invalid session token for plan "${input.taskId}".` }],
|
|
2318
|
+
isError: true
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
const actorName = await getGitHubUsername();
|
|
2322
|
+
const clientInfoName = getClientInfo();
|
|
2323
|
+
const { platform } = detectPlatform(clientInfoName);
|
|
2324
|
+
const agentDisplayName = getDisplayName(platform, actorName);
|
|
2325
|
+
const parsedThreadId = parseThreadId(input.threadId);
|
|
2326
|
+
logger.debug({ inputThreadId: input.threadId, parsedThreadId }, "Parsed thread ID from input");
|
|
2327
|
+
const reply = ThreadCommentSchema.parse({
|
|
2328
|
+
id: nanoid3(),
|
|
2329
|
+
userId: agentDisplayName,
|
|
2330
|
+
body: input.body,
|
|
2331
|
+
createdAt: Date.now()
|
|
2332
|
+
});
|
|
2333
|
+
let updateSucceeded = false;
|
|
2334
|
+
ydoc.transact(
|
|
2335
|
+
() => {
|
|
2336
|
+
const threadsMap = ydoc.getMap(YDOC_KEYS.THREADS);
|
|
2337
|
+
const threadDataRaw = threadsMap.get(parsedThreadId);
|
|
2338
|
+
if (!threadDataRaw || typeof threadDataRaw !== "object") {
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
const threadData = toPlainObject(threadDataRaw);
|
|
2342
|
+
if (!threadData) {
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
if (!("comments" in threadData)) {
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
const existingComments = Array.isArray(threadData.comments) ? threadData.comments : [];
|
|
2349
|
+
const updatedThread = {
|
|
2350
|
+
...threadData,
|
|
2351
|
+
comments: [...existingComments, reply]
|
|
2352
|
+
};
|
|
2353
|
+
threadsMap.set(parsedThreadId, updatedThread);
|
|
2354
|
+
logPlanEvent(ydoc, "comment_added", actorName, {
|
|
2355
|
+
commentId: reply.id
|
|
2356
|
+
});
|
|
2357
|
+
updateSucceeded = true;
|
|
2358
|
+
},
|
|
2359
|
+
actorName ? { actor: actorName } : void 0
|
|
2360
|
+
);
|
|
2361
|
+
if (!updateSucceeded) {
|
|
2362
|
+
return {
|
|
2363
|
+
content: [
|
|
2364
|
+
{
|
|
2365
|
+
type: "text",
|
|
2366
|
+
text: `Thread "${parsedThreadId}" not found in plan "${input.taskId}".${input.threadId !== parsedThreadId ? ` (parsed from input: "${input.threadId}")` : ""}`
|
|
2367
|
+
}
|
|
2368
|
+
],
|
|
2369
|
+
isError: true
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
logger.info(
|
|
2373
|
+
{ taskId: input.taskId, threadId: parsedThreadId, commentId: reply.id },
|
|
2374
|
+
"Thread reply added"
|
|
2375
|
+
);
|
|
2376
|
+
return {
|
|
2377
|
+
content: [
|
|
2378
|
+
{
|
|
2379
|
+
type: "text",
|
|
2380
|
+
text: `Reply added to thread!
|
|
2381
|
+
|
|
2382
|
+
Comment ID: ${reply.id}
|
|
2383
|
+
Thread ID: ${parsedThreadId}
|
|
2384
|
+
|
|
2385
|
+
The reply will appear in the comments panel when viewing this plan.`
|
|
2386
|
+
}
|
|
2387
|
+
]
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
|
|
2392
|
+
// src/tools/setup-review-notification.ts
|
|
2393
|
+
import { z as z12 } from "zod";
|
|
2394
|
+
var SetupReviewNotificationInput = z12.object({
|
|
2395
|
+
taskId: z12.string().describe("Task ID to monitor"),
|
|
2396
|
+
pollIntervalSeconds: z12.number().optional().default(30).describe("Polling interval in seconds (default: 30)")
|
|
2084
2397
|
});
|
|
2085
2398
|
var setupReviewNotificationTool = {
|
|
2086
2399
|
definition: {
|
|
@@ -2198,31 +2511,31 @@ The script:
|
|
|
2198
2511
|
|
|
2199
2512
|
// src/tools/update-block-content.ts
|
|
2200
2513
|
import { ServerBlockNoteEditor as ServerBlockNoteEditor5 } from "@blocknote/server-util";
|
|
2201
|
-
import { z as
|
|
2202
|
-
var BlockOperationSchema =
|
|
2203
|
-
|
|
2204
|
-
type:
|
|
2205
|
-
blockId:
|
|
2206
|
-
content:
|
|
2514
|
+
import { z as z13 } from "zod";
|
|
2515
|
+
var BlockOperationSchema = z13.discriminatedUnion("type", [
|
|
2516
|
+
z13.object({
|
|
2517
|
+
type: z13.literal("update"),
|
|
2518
|
+
blockId: z13.string().describe("The block ID to update (from read_plan output)"),
|
|
2519
|
+
content: z13.string().describe("New markdown content for this block")
|
|
2207
2520
|
}),
|
|
2208
|
-
|
|
2209
|
-
type:
|
|
2210
|
-
afterBlockId:
|
|
2211
|
-
content:
|
|
2521
|
+
z13.object({
|
|
2522
|
+
type: z13.literal("insert"),
|
|
2523
|
+
afterBlockId: z13.string().nullable().describe("Insert after this block ID (null = insert at beginning)"),
|
|
2524
|
+
content: z13.string().describe("Markdown content to insert as new block(s)")
|
|
2212
2525
|
}),
|
|
2213
|
-
|
|
2214
|
-
type:
|
|
2215
|
-
blockId:
|
|
2526
|
+
z13.object({
|
|
2527
|
+
type: z13.literal("delete"),
|
|
2528
|
+
blockId: z13.string().describe("The block ID to delete")
|
|
2216
2529
|
}),
|
|
2217
|
-
|
|
2218
|
-
type:
|
|
2219
|
-
content:
|
|
2530
|
+
z13.object({
|
|
2531
|
+
type: z13.literal("replace_all"),
|
|
2532
|
+
content: z13.string().describe("Complete markdown content to replace the entire plan")
|
|
2220
2533
|
})
|
|
2221
2534
|
]);
|
|
2222
|
-
var UpdateBlockContentInput =
|
|
2223
|
-
taskId:
|
|
2224
|
-
sessionToken:
|
|
2225
|
-
operations:
|
|
2535
|
+
var UpdateBlockContentInput = z13.object({
|
|
2536
|
+
taskId: z13.string().describe("The task ID to modify"),
|
|
2537
|
+
sessionToken: z13.string().describe("Session token from create_task"),
|
|
2538
|
+
operations: z13.array(BlockOperationSchema).min(1).describe("Array of operations to perform atomically")
|
|
2226
2539
|
});
|
|
2227
2540
|
var updateBlockContentTool = {
|
|
2228
2541
|
definition: {
|
|
@@ -2483,15 +2796,15 @@ async function applyOperation(blocks, operation, editor) {
|
|
|
2483
2796
|
|
|
2484
2797
|
// src/tools/update-task.ts
|
|
2485
2798
|
import { ServerBlockNoteEditor as ServerBlockNoteEditor6 } from "@blocknote/server-util";
|
|
2486
|
-
import { nanoid as
|
|
2487
|
-
import { z as
|
|
2799
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
2800
|
+
import { z as z14 } from "zod";
|
|
2488
2801
|
function buildStatusTransition(targetStatus, actorName) {
|
|
2489
2802
|
const now = Date.now();
|
|
2490
2803
|
switch (targetStatus) {
|
|
2491
2804
|
case "pending_review":
|
|
2492
2805
|
return {
|
|
2493
2806
|
status: "pending_review",
|
|
2494
|
-
reviewRequestId:
|
|
2807
|
+
reviewRequestId: nanoid4()
|
|
2495
2808
|
};
|
|
2496
2809
|
case "changes_requested":
|
|
2497
2810
|
return {
|
|
@@ -2517,12 +2830,12 @@ function buildStatusTransition(targetStatus, actorName) {
|
|
|
2517
2830
|
return null;
|
|
2518
2831
|
}
|
|
2519
2832
|
}
|
|
2520
|
-
var UpdateTaskInput =
|
|
2521
|
-
taskId:
|
|
2522
|
-
sessionToken:
|
|
2523
|
-
title:
|
|
2524
|
-
status:
|
|
2525
|
-
tags:
|
|
2833
|
+
var UpdateTaskInput = z14.object({
|
|
2834
|
+
taskId: z14.string().describe("The task ID to update"),
|
|
2835
|
+
sessionToken: z14.string().describe("Session token from create_task"),
|
|
2836
|
+
title: z14.string().optional().describe("New title"),
|
|
2837
|
+
status: z14.enum(["draft", "pending_review", "changes_requested", "in_progress", "completed"]).optional().describe("New status"),
|
|
2838
|
+
tags: z14.array(z14.string()).optional().describe("Updated tags (replaces existing tags)")
|
|
2526
2839
|
});
|
|
2527
2840
|
var updateTaskTool = {
|
|
2528
2841
|
definition: {
|
|
@@ -2685,8 +2998,8 @@ function getToolResultText(result) {
|
|
|
2685
2998
|
return typeof text === "string" ? text : "";
|
|
2686
2999
|
}
|
|
2687
3000
|
async function serializeError(error) {
|
|
2688
|
-
const { z:
|
|
2689
|
-
if (error instanceof
|
|
3001
|
+
const { z: z16 } = await import("zod");
|
|
3002
|
+
if (error instanceof z16.ZodError) {
|
|
2690
3003
|
const formattedIssues = error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
2691
3004
|
const message2 = `Validation error:
|
|
2692
3005
|
${formattedIssues}`;
|
|
@@ -2720,7 +3033,7 @@ ${formattedIssues}`;
|
|
|
2720
3033
|
}
|
|
2721
3034
|
var BUNDLED_DOCS = `Execute TypeScript code that calls Shipyard APIs. Use this for multi-step workflows to reduce round-trips.
|
|
2722
3035
|
|
|
2723
|
-
\u26A0\uFE0F IMPORTANT LIMITATION: Dynamic imports (\`await import()\`) are NOT supported in the VM execution context. Use only the pre-provided functions in the execution environment (createTask, readTask, readDiffComments, updateTask, addArtifact, completeTask, updateBlockContent, linkPR, requestUserInput, regenerateSessionToken, postUpdate). All necessary APIs are already available in the sandbox.
|
|
3036
|
+
\u26A0\uFE0F IMPORTANT LIMITATION: Dynamic imports (\`await import()\`) are NOT supported in the VM execution context. Use only the pre-provided functions in the execution environment (createTask, readTask, readDiffComments, updateTask, addArtifact, completeTask, updateBlockContent, linkPR, replyToThreadComment, replyToDiffComment, requestUserInput, regenerateSessionToken, postUpdate). All necessary APIs are already available in the sandbox.
|
|
2724
3037
|
|
|
2725
3038
|
## Available APIs
|
|
2726
3039
|
|
|
@@ -2909,6 +3222,56 @@ console.log('Linked:', pr.title, pr.status);
|
|
|
2909
3222
|
|
|
2910
3223
|
---
|
|
2911
3224
|
|
|
3225
|
+
### replyToThreadComment(opts): Promise<string>
|
|
3226
|
+
Reply to a BlockNote inline thread comment.
|
|
3227
|
+
|
|
3228
|
+
Parameters:
|
|
3229
|
+
- taskId (string): Task ID
|
|
3230
|
+
- sessionToken (string): Session token
|
|
3231
|
+
- threadId (string): Thread ID from readTask output (format: [thread:abc123])
|
|
3232
|
+
- body (string): Reply text
|
|
3233
|
+
|
|
3234
|
+
Returns: Success message with comment ID and thread ID
|
|
3235
|
+
|
|
3236
|
+
Example:
|
|
3237
|
+
\`\`\`typescript
|
|
3238
|
+
const result = await replyToThreadComment({
|
|
3239
|
+
taskId,
|
|
3240
|
+
sessionToken,
|
|
3241
|
+
threadId: 'thread-xyz789',
|
|
3242
|
+
body: 'Good point! I\\'ll add that validation.'
|
|
3243
|
+
});
|
|
3244
|
+
console.log(result); // "Reply added to thread!..."
|
|
3245
|
+
\`\`\`
|
|
3246
|
+
|
|
3247
|
+
---
|
|
3248
|
+
|
|
3249
|
+
### replyToDiffComment(opts): Promise<string>
|
|
3250
|
+
Reply to a PR review comment or local diff comment.
|
|
3251
|
+
|
|
3252
|
+
Parameters:
|
|
3253
|
+
- taskId (string): Task ID
|
|
3254
|
+
- sessionToken (string): Session token
|
|
3255
|
+
- commentId (string): Comment ID from readDiffComments (format: [pr:abc123] or [local:abc123])
|
|
3256
|
+
- body (string): Reply text
|
|
3257
|
+
|
|
3258
|
+
Returns: Success message with reply comment ID and parent comment ID
|
|
3259
|
+
|
|
3260
|
+
Automatically detects whether the comment is a PR or local diff comment.
|
|
3261
|
+
|
|
3262
|
+
Example:
|
|
3263
|
+
\`\`\`typescript
|
|
3264
|
+
const result = await replyToDiffComment({
|
|
3265
|
+
taskId,
|
|
3266
|
+
sessionToken,
|
|
3267
|
+
commentId: 'pr:xyz789',
|
|
3268
|
+
body: 'Good catch! I\\'ll fix that in the next commit.'
|
|
3269
|
+
});
|
|
3270
|
+
console.log(result); // "Reply added to PR review comment!..."
|
|
3271
|
+
\`\`\`
|
|
3272
|
+
|
|
3273
|
+
---
|
|
3274
|
+
|
|
2912
3275
|
### requestUserInput(opts): Promise<{ success, response?, status, reason? }>
|
|
2913
3276
|
Request input from the user via browser modal.
|
|
2914
3277
|
|
|
@@ -3250,8 +3613,8 @@ const result = await addArtifact({
|
|
|
3250
3613
|
return { taskId: task.taskId, snapshotUrl: result.snapshotUrl };
|
|
3251
3614
|
\`\`\`
|
|
3252
3615
|
`;
|
|
3253
|
-
var ExecuteCodeInput =
|
|
3254
|
-
code:
|
|
3616
|
+
var ExecuteCodeInput = z15.object({
|
|
3617
|
+
code: z15.string().describe("TypeScript code to execute")
|
|
3255
3618
|
});
|
|
3256
3619
|
var scriptTracker = [];
|
|
3257
3620
|
async function createTask(opts) {
|
|
@@ -3394,8 +3757,16 @@ async function setupReviewNotification(taskId, pollIntervalSeconds) {
|
|
|
3394
3757
|
const script = scriptMatch?.[1] || "";
|
|
3395
3758
|
return { script, fullResponse: text };
|
|
3396
3759
|
}
|
|
3760
|
+
async function replyToThreadComment(opts) {
|
|
3761
|
+
const result = await replyToThreadCommentTool.handler(opts);
|
|
3762
|
+
return getToolResultText(result);
|
|
3763
|
+
}
|
|
3764
|
+
async function replyToDiffComment(opts) {
|
|
3765
|
+
const result = await replyToDiffCommentTool.handler(opts);
|
|
3766
|
+
return getToolResultText(result);
|
|
3767
|
+
}
|
|
3397
3768
|
async function requestUserInput(opts) {
|
|
3398
|
-
const { InputRequestManager } = await import("./input-request-manager-
|
|
3769
|
+
const { InputRequestManager } = await import("./input-request-manager-FK4REBEZ.js");
|
|
3399
3770
|
const ydoc = await getOrCreateDoc(PLAN_INDEX_DOC_NAME);
|
|
3400
3771
|
const manager = new InputRequestManager();
|
|
3401
3772
|
if ("questions" in opts && opts.questions) {
|
|
@@ -3614,6 +3985,8 @@ var executeCodeTool = {
|
|
|
3614
3985
|
completeTask,
|
|
3615
3986
|
updateBlockContent,
|
|
3616
3987
|
linkPR: linkPR2,
|
|
3988
|
+
replyToThreadComment,
|
|
3989
|
+
replyToDiffComment,
|
|
3617
3990
|
requestUserInput,
|
|
3618
3991
|
regenerateSessionToken,
|
|
3619
3992
|
postUpdate,
|
|
@@ -3722,6 +4095,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3722
4095
|
tools: [executeCodeTool.definition]
|
|
3723
4096
|
}));
|
|
3724
4097
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
4098
|
+
const clientInfo = server.getClientVersion();
|
|
4099
|
+
if (clientInfo?.name) {
|
|
4100
|
+
setClientInfo(clientInfo.name);
|
|
4101
|
+
logger.info(
|
|
4102
|
+
{ clientName: clientInfo.name, clientVersion: clientInfo.version },
|
|
4103
|
+
"MCP client info captured from tool call"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
3725
4106
|
const { name, arguments: args } = request.params;
|
|
3726
4107
|
if (name === TOOL_NAMES.EXECUTE_CODE) {
|
|
3727
4108
|
return await executeCodeTool.handler(args ?? {});
|
package/apps/server/dist/{input-request-manager-QT4IVCLS.js → input-request-manager-FK4REBEZ.js}
RENAMED
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
createMultiQuestionInputRequest,
|
|
6
6
|
getOrCreateDoc,
|
|
7
7
|
logPlanEvent
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6TW62VVK.js";
|
|
9
9
|
import {
|
|
10
10
|
logger
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DLDKEWOB.js";
|
|
12
12
|
|
|
13
13
|
// src/services/input-request-manager.ts
|
|
14
14
|
function formatDuration(totalSeconds) {
|