@rallycry/conveyor-mcp 4.3.13 → 4.3.15

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/cli.js CHANGED
@@ -140,7 +140,7 @@ function registerUpdateProjectSettings(server2, conn2) {
140
140
  projectId: z3.string().optional().describe("Target Conveyor project ID"),
141
141
  name: z3.string().optional().describe("New project name"),
142
142
  description: z3.string().optional().describe("New project description"),
143
- settings: z3.record(z3.unknown()).optional().describe(
143
+ settings: z3.record(z3.string(), z3.unknown()).optional().describe(
144
144
  "Deep-merged patch of the project settings JSON (JSON Merge Patch semantics: null deletes a key; advanced)"
145
145
  ),
146
146
  defaultPmAgentId: z3.string().nullable().optional().describe("Default PM agent ID"),
@@ -447,13 +447,13 @@ var childTaskIdForMerge = f.string({
447
447
  var approveAndMergePrContract = defineToolContract({
448
448
  name: "approve_and_merge_pr",
449
449
  agent: {
450
- description: "Approve and merge a child task's PR. Preconditions: child in ReviewPR. Returns { merged }: true = merged (status\u2192ReviewDev); false = automerge queued, wait for ReviewDev.",
450
+ description: "Approve and merge a child task's PR. Preconditions: child in ReviewPR. Returns { merged }: true = merged (status\u2192ReviewDev); false = automerge queued, wait for ReviewDev. Requires project Admin, or a sub-project merge grant covering every changed file; release PRs require Admin.",
451
451
  fields: {
452
452
  childTaskId: childTaskIdForMerge
453
453
  }
454
454
  },
455
455
  mcp: {
456
- description: "Approve and merge a child task's pull request. Pass projectId to target a specific project; otherwise the configured default project is used. Only succeeds if all CI/CD checks are passing. The child task must be in ReviewPR status with a PR.",
456
+ description: "Approve and merge a child task's pull request. Pass projectId to target a specific project; otherwise the configured default project is used. Only succeeds if all CI/CD checks are passing. The child task must be in ReviewPR status with a PR. Requires project Admin, or a sub-project merge grant covering every changed file; release PRs require Admin.",
457
457
  fields: {
458
458
  projectId: mcpProjectId,
459
459
  childTaskId: childTaskIdForMerge
@@ -870,13 +870,13 @@ var getAttachmentContract = defineToolContract({
870
870
  var uploadAttachmentContract = defineToolContract({
871
871
  name: "upload_attachment",
872
872
  agent: {
873
- description: "Upload an image file (e.g. a Playwright screenshot) as a task attachment AND post it to the task chat in one step \u2014 no follow-up post_to_chat call needed. Supports png/jpg/gif/webp.",
873
+ description: "Upload a file (doc, notes, data, diagram, screenshot \u2014 any file type, up to 25MB) as a task attachment AND post it to the task chat in one step \u2014 no follow-up post_to_chat call needed. This is how you deliver a file the user should keep: it attaches to the card. Never publish deliverables as an external Claude artifact.",
874
874
  fields: {
875
875
  path: f.string({
876
- desc: "Path to the image file \u2014 absolute, or relative to the workspace root"
876
+ desc: "Path to the file \u2014 absolute, or relative to the workspace root"
877
877
  }),
878
878
  title: f.optional(
879
- f.string({ desc: "Short caption posted with the image (defaults to the file name)" })
879
+ f.string({ desc: "Short caption posted with the file (defaults to the file name)" })
880
880
  )
881
881
  }
882
882
  },
@@ -988,7 +988,17 @@ import { z as z4 } from "zod";
988
988
  function mcpShape(surface) {
989
989
  return compileShape(z4, surface.fields);
990
990
  }
991
- function registerContractTool(server2, contract, handler) {
991
+ function registerContractTool(server2, contract, handler, options) {
992
+ if (options?.alwaysLoad) {
993
+ registerHotTool(
994
+ server2,
995
+ contract.name,
996
+ contract.mcp.description,
997
+ mcpShape(contract.mcp),
998
+ handler
999
+ );
1000
+ return;
1001
+ }
992
1002
  server2.tool(
993
1003
  contract.name,
994
1004
  contract.mcp.description,
@@ -996,6 +1006,17 @@ function registerContractTool(server2, contract, handler) {
996
1006
  handler
997
1007
  );
998
1008
  }
1009
+ function registerHotTool(server2, name, description, schema, handler) {
1010
+ server2.registerTool(
1011
+ name,
1012
+ {
1013
+ description,
1014
+ inputSchema: schema,
1015
+ _meta: { "anthropic/alwaysLoad": true }
1016
+ },
1017
+ handler
1018
+ );
1019
+ }
999
1020
 
1000
1021
  // src/tools/tasks-format.ts
1001
1022
  var CLI_EVENT_FORMATTERS = {
@@ -1009,9 +1030,23 @@ var CLI_EVENT_FORMATTERS = {
1009
1030
  start_command_output: (data) => `[${data.stream ?? "stdout"}] ${String(data.data ?? "")}`,
1010
1031
  turn_end: (data) => `Turn complete (${Array.isArray(data.toolCalls) ? data.toolCalls.length : 0} tool calls)`
1011
1032
  };
1033
+ var EVENT_SUMMARY_MAX_CHARS = 1200;
1012
1034
  function formatCliEventSummary(type, data) {
1013
1035
  const formatter = CLI_EVENT_FORMATTERS[type];
1014
- return formatter ? formatter(data) : JSON.stringify(data);
1036
+ const summary = formatter ? formatter(data) : JSON.stringify(data);
1037
+ if (summary.length <= EVENT_SUMMARY_MAX_CHARS) return summary;
1038
+ return `${summary.slice(0, EVENT_SUMMARY_MAX_CHARS)}\u2026[+${summary.length - EVENT_SUMMARY_MAX_CHARS}c]`;
1039
+ }
1040
+ var CHAT_MESSAGE_PREVIEW_CHARS = 2e3;
1041
+ function compactChatMessage(message) {
1042
+ if (typeof message !== "object" || message === null) return message;
1043
+ const obj = { ...message };
1044
+ const content = obj.content;
1045
+ if (typeof content === "string" && content.length > CHAT_MESSAGE_PREVIEW_CHARS) {
1046
+ const overflow = content.length - CHAT_MESSAGE_PREVIEW_CHARS;
1047
+ obj.content = `${content.slice(0, CHAT_MESSAGE_PREVIEW_CHARS)}\u2026[+${overflow}c truncated]`;
1048
+ }
1049
+ return obj;
1015
1050
  }
1016
1051
  function formatLegacyComputeSession(s) {
1017
1052
  return ` - session ${s.id} [${s.provider}] pod=${s.instanceName ?? "-"} status=${s.status} runner=${s.agentRunnerStatus ?? "-"} lastHeartbeat=${s.lastHeartbeatAt ?? "-"} created=${s.createdAt} stopped=${s.stoppedAt ?? "-"}` + (s.deletionRequestedAt ? ` deletionRequested=${s.deletionRequestedAt} (attempts ${s.deletionAttempts})` : "") + (s.lastAgentEvent ? ` lastAgentEvent=${s.lastAgentEvent}` : "");
@@ -1030,19 +1065,32 @@ function formatWorkspace(w) {
1030
1065
  lines.push(...w.sessions.map(formatWorkspaceSession));
1031
1066
  return lines;
1032
1067
  }
1033
- function formatTaskSessionState(t) {
1068
+ function latestByCreatedAt(items, limit) {
1069
+ if (items.length <= limit) return { kept: items, omitted: 0 };
1070
+ const sorted = [...items].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1071
+ return { kept: sorted.slice(0, limit), omitted: items.length - limit };
1072
+ }
1073
+ function formatTaskSessionState(t, limit = 20) {
1034
1074
  const review = t.codeReviewStatus ? ` codeReview=${t.codeReviewStatus} (attempts ${t.codeReviewAttempts})` : "";
1035
1075
  const kind = t.reviewTargetTaskId ? `review card for ${t.reviewTargetTaskId}` : t.type;
1036
1076
  const lines = [`# ${t.slug} \u2014 ${t.title} [${kind}, status ${t.status}]${review}`];
1037
1077
  if (t.sessions.length === 0) {
1038
1078
  lines.push(" (no compute sessions)");
1039
1079
  } else {
1040
- lines.push(...t.sessions.map(formatLegacyComputeSession));
1080
+ const { kept, omitted } = latestByCreatedAt(t.sessions, limit);
1081
+ lines.push(...kept.map(formatLegacyComputeSession));
1082
+ if (omitted > 0) {
1083
+ lines.push(` (\u2026 +${omitted} older compute sessions \u2014 pass a higher limit to see them)`);
1084
+ }
1041
1085
  }
1042
1086
  if (t.workspaces.length === 0) {
1043
1087
  lines.push(" (no v3 workspaces)");
1044
1088
  } else {
1045
- lines.push(...t.workspaces.flatMap(formatWorkspace));
1089
+ const { kept, omitted } = latestByCreatedAt(t.workspaces, limit);
1090
+ lines.push(...kept.flatMap(formatWorkspace));
1091
+ if (omitted > 0) {
1092
+ lines.push(` (\u2026 +${omitted} older workspaces \u2014 pass a higher limit to see them)`);
1093
+ }
1046
1094
  }
1047
1095
  return lines;
1048
1096
  }
@@ -1076,7 +1124,8 @@ var BOARD_ASSIGN = z5.string().nullable().optional().describe(
1076
1124
  "Assign the card to a sub-project board. Omit to use the connection's default board (CONVEYOR_SUBPROJECT_ID) when set, else the parent project; pass null to force the parent project. Use list_accessible_subprojects to find board IDs."
1077
1125
  );
1078
1126
  function registerListTasks(server2, conn2) {
1079
- server2.tool(
1127
+ registerHotTool(
1128
+ server2,
1080
1129
  "list_tasks",
1081
1130
  "List project cards, optionally filtered by card type, status, or assignment (a specific assignee, or unassigned tasks). Defaults to type=task \u2014 pass typeFilters to list incidents/suggestions. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Pass projectId to target a specific project; otherwise the configured default project is used. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.",
1082
1131
  {
@@ -1099,10 +1148,15 @@ function registerListTasks(server2, conn2) {
1099
1148
  );
1100
1149
  }
1101
1150
  function registerGetTask(server2, conn2) {
1102
- registerContractTool(server2, getTaskContract, async (params) => {
1103
- const task = await conn2.getTask(params.taskId, params.projectId);
1104
- return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1105
- });
1151
+ registerContractTool(
1152
+ server2,
1153
+ getTaskContract,
1154
+ async (params) => {
1155
+ const task = await conn2.getTask(params.taskId, params.projectId);
1156
+ return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1157
+ },
1158
+ { alwaysLoad: true }
1159
+ );
1106
1160
  }
1107
1161
  function registerGetCardBySlug(server2, conn2) {
1108
1162
  server2.tool(
@@ -1209,27 +1263,34 @@ function registerMoveCard(server2, conn2) {
1209
1263
  }
1210
1264
  function registerChatTools(server2, conn2) {
1211
1265
  registerContractTool(server2, readTaskChatContract, async (params) => {
1212
- const messages = await conn2.getTaskChat(params.taskId, params.limit, params.projectId);
1213
- return { content: [{ type: "text", text: JSON.stringify(messages, null, 2) }] };
1214
- });
1215
- registerContractTool(server2, postToChatContract, async (params) => {
1216
- const text = params.content ?? params.message;
1217
- if (text === void 0) {
1218
- return {
1219
- content: [
1220
- {
1221
- type: "text",
1222
- text: "Nothing to post \u2014 provide `content` (or its alias `message`)."
1223
- }
1224
- ]
1225
- };
1226
- }
1227
- await conn2.postToTaskChat(params.taskId, text, params.projectId);
1228
- return { content: [{ type: "text", text: "Message posted" }] };
1266
+ const messages = await conn2.getTaskChat(params.taskId, params.limit ?? 50, params.projectId);
1267
+ const compacted = messages.map(compactChatMessage);
1268
+ return { content: [{ type: "text", text: JSON.stringify(compacted, null, 2) }] };
1229
1269
  });
1270
+ registerContractTool(
1271
+ server2,
1272
+ postToChatContract,
1273
+ async (params) => {
1274
+ const text = params.content ?? params.message;
1275
+ if (text === void 0) {
1276
+ return {
1277
+ content: [
1278
+ {
1279
+ type: "text",
1280
+ text: "Nothing to post \u2014 provide `content` (or its alias `message`)."
1281
+ }
1282
+ ]
1283
+ };
1284
+ }
1285
+ await conn2.postToTaskChat(params.taskId, text, params.projectId);
1286
+ return { content: [{ type: "text", text: "Message posted" }] };
1287
+ },
1288
+ { alwaysLoad: true }
1289
+ );
1230
1290
  }
1231
1291
  function registerGetTaskCli(server2, conn2) {
1232
- server2.tool(
1292
+ registerHotTool(
1293
+ server2,
1233
1294
  "get_task_logs",
1234
1295
  "Read CLI execution logs from a task. Pass projectId to target a specific project; otherwise the configured default project is used. Returns agent reasoning, tool calls, setup output, and other execution events. For human chat use read_task_chat.",
1235
1296
  {
@@ -1258,11 +1319,12 @@ function registerGetTaskSessions(server2, conn2) {
1258
1319
  "Read compute-session state for a task: legacy CodespaceSession rows plus v3 workspaces, including purpose=review review workspaces. Shows pod identity, liveness, lifecycle, and code-review claim state. Use to diagnose stalled/dead agents or code-review runs. Pass projectId to target a specific project; otherwise the configured default project is used.",
1259
1320
  {
1260
1321
  projectId: z5.string().optional().describe("Target Conveyor project ID"),
1261
- taskId: z5.string().describe("The task ID or slug")
1322
+ taskId: z5.string().describe("The task ID or slug"),
1323
+ limit: z5.number().int().min(1).max(200).optional().describe("Max sessions/workspaces listed per task, newest first (default 20)")
1262
1324
  },
1263
- async ({ taskId, projectId: projectId2 }) => {
1325
+ async ({ taskId, projectId: projectId2, limit }) => {
1264
1326
  const tasks = await conn2.getTaskSessions(taskId, projectId2);
1265
- const lines = tasks.flatMap(formatTaskSessionState);
1327
+ const lines = tasks.flatMap((t) => formatTaskSessionState(t, limit ?? 20));
1266
1328
  return {
1267
1329
  content: [
1268
1330
  { type: "text", text: lines.join("\n") || "No sessions found for this task." }
@@ -1272,7 +1334,8 @@ function registerGetTaskSessions(server2, conn2) {
1272
1334
  );
1273
1335
  }
1274
1336
  function registerSearchTasks(server2, conn2) {
1275
- server2.tool(
1337
+ registerHotTool(
1338
+ server2,
1276
1339
  "search_tasks",
1277
1340
  "Search cards by tag name, text query, status, type, and/or assignment. Defaults to type=task \u2014 pass typeFilters to include incidents/suggestions. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Pass projectId to target a specific project; otherwise the configured default project is used. Use tag names like 'agent-runner', not IDs. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.",
1278
1341
  {
@@ -1644,12 +1707,17 @@ function registerAttachmentTools(server2, conn2) {
1644
1707
 
1645
1708
  // src/tools/pull-request.ts
1646
1709
  function registerPullRequestTools(server2, conn2) {
1647
- registerContractTool(server2, createPullRequestContract, async (params) => {
1648
- const result = await conn2.createPullRequest(params);
1649
- return {
1650
- content: [{ type: "text", text: `PR #${result.prNumber} opened: ${result.prUrl}` }]
1651
- };
1652
- });
1710
+ registerContractTool(
1711
+ server2,
1712
+ createPullRequestContract,
1713
+ async (params) => {
1714
+ const result = await conn2.createPullRequest(params);
1715
+ return {
1716
+ content: [{ type: "text", text: `PR #${result.prNumber} opened: ${result.prUrl}` }]
1717
+ };
1718
+ },
1719
+ { alwaysLoad: true }
1720
+ );
1653
1721
  }
1654
1722
 
1655
1723
  // src/tools/subtasks.ts
@@ -2183,7 +2251,8 @@ async function runQueryGrafanaLogs(conn2, params, now = Date.now) {
2183
2251
  `entries=${result.entries.length}`
2184
2252
  ].join(" ");
2185
2253
  const lines = result.entries.map(formatLogEntryLine);
2186
- const footer = result.hasMore ? ["-- hit the limit: narrow the window (startTime/endTime) for older lines"] : [];
2254
+ const oldest = result.entries.map((e) => e.timestamp).sort()[0];
2255
+ const footer = result.hasMore ? [`-- hit the limit: older lines exist \u2014 pass endTime="${oldest}" to page further back`] : [];
2187
2256
  if (lines.length === 0) {
2188
2257
  return [
2189
2258
  header,