@rallycry/conveyor-agent 11.0.39 → 11.0.41

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.
@@ -8,7 +8,7 @@ import {
8
8
  readWorkspaceBytes,
9
9
  statWorkspacePath,
10
10
  workspacePathExists
11
- } from "./chunk-B4YQABDZ.js";
11
+ } from "./chunk-CG7AHC25.js";
12
12
  import {
13
13
  reportBootMilestone
14
14
  } from "./chunk-Q4FQOJ7D.js";
@@ -97,7 +97,7 @@ import {
97
97
  statWorkspacePath,
98
98
  updateRemoteToken,
99
99
  verifyGitCredential
100
- } from "./chunk-B4YQABDZ.js";
100
+ } from "./chunk-CG7AHC25.js";
101
101
  import {
102
102
  registerBootMilestoneSocketFallback,
103
103
  reportBootMilestone
@@ -7350,6 +7350,7 @@ function defineToolContract(contract) {
7350
7350
  var mcpProjectId = f.optional(f.string({ desc: "Target Conveyor project ID" }));
7351
7351
  var cardDescriptionDesc = (lead) => `${lead} \u2014 ${CARD_DESCRIPTION_FIELD_HINT}`;
7352
7352
  var storyPointValueDesc = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
7353
+ var priorityValueDesc = "Priority value from manage_priorities list (1 = the project's highest level; seeded defaults are 1=High, 2=Medium, 3=Low)";
7353
7354
  var getTaskContract = defineToolContract({
7354
7355
  name: "get_task",
7355
7356
  agent: {
@@ -7583,7 +7584,7 @@ var updateTaskContract = defineToolContract({
7583
7584
  }
7584
7585
  },
7585
7586
  mcp: {
7586
- description: "Update task fields: title, description, plan, status, risk, story points, assignment, githubBranch, onHold, or tags. Set status to claim a card (InProgress), triage it (Open), or cancel it (Cancelled); for review approvals prefer approve_task / request_changes, which guard against stale-state races. Set onHold to park a card (true) or bring it back to the active board (false) without cancelling it. Tags are additive/subtractive \u2014 pass addTags/removeTags with tag names (not a replace-set). Pass projectId to target a specific project; otherwise the configured default project is used. Moving a task beyond Planning auto-fills any missing icon, story points, and agent assignment \u2014 don't spend turns on them; pass storyPointValue only to correct the sizing yourself. For subtasks use update_subtask.",
7587
+ description: "Update task fields: title, description, plan, status, risk, story points, priority, assignment, githubBranch, onHold, or tags. Set status to claim a card (InProgress), triage it (Open), or cancel it (Cancelled); for review approvals prefer approve_task / request_changes, which guard against stale-state races. Set onHold to park a card (true) or bring it back to the active board (false) without cancelling it. Tags are additive/subtractive \u2014 pass addTags/removeTags with tag names (not a replace-set). Pass projectId to target a specific project; otherwise the configured default project is used. Moving a task beyond Planning auto-fills any missing icon, story points, and agent assignment \u2014 don't spend turns on them; pass storyPointValue only to correct the sizing yourself. For subtasks use update_subtask.",
7587
7588
  fields: {
7588
7589
  projectId: mcpProjectId,
7589
7590
  taskId: f.string({ desc: "The task ID" }),
@@ -7605,6 +7606,13 @@ var updateTaskContract = defineToolContract({
7605
7606
  })
7606
7607
  )
7607
7608
  ),
7609
+ priorityValue: f.optional(
7610
+ f.nullable(
7611
+ f.number({
7612
+ desc: `${priorityValueDesc}. The levels are per-project \u2014 a value the project has not configured is rejected. Pass null to clear it.`
7613
+ })
7614
+ )
7615
+ ),
7608
7616
  assignedUserId: f.optional(f.nullable(f.string({ desc: "User ID to assign, or null" }))),
7609
7617
  subProjectId: f.optional(
7610
7618
  f.nullable(
@@ -8066,13 +8074,13 @@ var agentTaskIdOrSlug = f.optional(
8066
8074
  var listTaskFilesContract = defineToolContract({
8067
8075
  name: "list_task_files",
8068
8076
  agent: {
8069
- description: "List all files attached to this task with metadata. Use before fetching a specific file to see what is available and how large each is. For file contents use get_attachment. Pass task_id to list another card's files in this project, for example the pack parent's source documents.",
8077
+ description: "List all files attached to this task with metadata. Showcase files (the card's deliverables, `showcase: true` on each row) list first; a row with `supersedesFileId` is a newer version of that file. Use before fetching a specific file to see what is available and how large each is. For file contents use get_attachment. Pass task_id to list another card's files in this project, for example the pack parent's source documents.",
8070
8078
  fields: {
8071
8079
  task_id: agentTaskIdOrSlug
8072
8080
  }
8073
8081
  },
8074
8082
  mcp: {
8075
- description: "List all files attached to a task with metadata (no contents \u2014 fast and small). Pass projectId to target a specific project; otherwise the configured default project is used. Use before fetching a specific file to see what is available and how large each is. For file contents use get_attachment.",
8083
+ description: "List all files attached to a task with metadata (no contents \u2014 fast and small). Showcase files (the card's deliverables, `showcase: true` on each row) list first; a row with `supersedesFileId` is a newer version of that file. Pass projectId to target a specific project; otherwise the configured default project is used. Use before fetching a specific file to see what is available and how large each is. For file contents use get_attachment.",
8076
8084
  fields: {
8077
8085
  projectId: mcpProjectId,
8078
8086
  taskId: mcpTaskIdOrSlug
@@ -8116,10 +8124,20 @@ var attachmentTags = f.optional(
8116
8124
  desc: `Glossary tag names this file is a relevant example of. MUST be a JSON array of quoted strings, e.g. ["ops-hub", "platform-support"] \u2014 bare unquoted words are invalid JSON and fail the whole call before it is parsed (21 fleet calls in one week died this way). Use it when the file shows a tagged entity in a particular state: the tag's page lists its recent tagged attachments, so a reader can see what the entity looks like across the app and spot visual changes over time. Names are matched case-insensitively within the project; a name that matches no tag is reported back and never fails the upload. Max 5.`
8117
8125
  })
8118
8126
  );
8127
+ var attachmentShowcase = f.optional(
8128
+ f.boolean({
8129
+ desc: "Mark this file as a showcase: the finished result of the card. Showcase files render first with a thumbnail on the card, are what a tag gallery shows, always post to chat, get a long-lived link, are kept past the 90-day purge, and are appended to the PR body under '## Showcase'. Use it for the final capture, not for scoping or before shots."
8130
+ })
8131
+ );
8132
+ var attachmentSupersedes = f.optional(
8133
+ f.string({
8134
+ desc: "File id (from list_task_files) of an older file on this card that this upload replaces, e.g. the previous cut of a video. The old file loses showcase and collapses under the new one as history; the new one inherits its showcase flag and tag labels. Naming a file that already has a newer version replaces the newest one. A bad id never fails the upload \u2014 the reason comes back instead."
8135
+ })
8136
+ );
8119
8137
  var uploadAttachmentContract = defineToolContract({
8120
8138
  name: "upload_attachment",
8121
8139
  agent: {
8122
- 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. Pass `tags` when the file is a good example of a glossary tag in some state.",
8140
+ 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. Pass `showcase: true` for the capture that shows the finished result, `supersedes` with the old file's id when this is a newer version of a file already on the card, and `tags` when the file is a good example of a glossary tag in some state.",
8123
8141
  fields: {
8124
8142
  path: f.string({
8125
8143
  desc: "Path to the file \u2014 absolute, or relative to the workspace root"
@@ -8132,11 +8150,13 @@ var uploadAttachmentContract = defineToolContract({
8132
8150
  desc: "Alias of `title` (the external conveyor-mcp surface names this field `comment`). Provide at most one of the two."
8133
8151
  })
8134
8152
  ),
8135
- tags: attachmentTags
8153
+ tags: attachmentTags,
8154
+ showcase: attachmentShowcase,
8155
+ supersedes: attachmentSupersedes
8136
8156
  }
8137
8157
  },
8138
8158
  mcp: {
8139
- description: "Upload a local file as a task attachment (any file type, up to 25MB). Pass projectId to target a specific project; otherwise the configured default project is used. The file appears under the task's Files. Pass `comment` to also post it to the task chat in the same step, and `tags` when the file is a good example of a glossary tag in some state.",
8159
+ description: "Upload a local file as a task attachment (any file type, up to 25MB). Pass projectId to target a specific project; otherwise the configured default project is used. The file appears under the task's Files. Pass `comment` to also post it to the task chat in the same step, `showcase: true` for the capture that shows the finished result (a showcase file always posts to chat), `supersedes` with the old file's id when this is a newer version of a file already on the card, and `tags` when the file is a good example of a glossary tag in some state.",
8140
8160
  fields: {
8141
8161
  projectId: mcpProjectId,
8142
8162
  taskId: mcpTaskIdOrSlug,
@@ -8150,6 +8170,8 @@ var uploadAttachmentContract = defineToolContract({
8150
8170
  })
8151
8171
  ),
8152
8172
  tags: attachmentTags,
8173
+ showcase: attachmentShowcase,
8174
+ supersedes: attachmentSupersedes,
8153
8175
  mimeType: f.optional(
8154
8176
  f.string({ desc: "Override the mime type inferred from the file extension" })
8155
8177
  )
@@ -8174,11 +8196,35 @@ var deleteAttachmentContract = defineToolContract({
8174
8196
  }
8175
8197
  }
8176
8198
  });
8199
+ var SET_SHOWCASE_BEHAVIOR = "Mark an already-uploaded attachment as the card's showcase (`showcase: true`) or clear the flag (`showcase: false`) \u2014 the same flag upload_attachment sets at upload time, so promote an earlier capture here instead of uploading it again. A flip to showcase posts the file to the task chat once; a file that a chat message already carries is not posted again. `fileId` comes from list_task_files.";
8200
+ var showcaseFlag = f.boolean({
8201
+ desc: "true marks the file as the card's showcase; false clears the flag."
8202
+ });
8203
+ var setShowcaseContract = defineToolContract({
8204
+ name: "set_showcase",
8205
+ agent: {
8206
+ description: `${SET_SHOWCASE_BEHAVIOR} A pod can only change files on its own card.`,
8207
+ fields: {
8208
+ fileId: f.string({ desc: "The file ID to change" }),
8209
+ showcase: showcaseFlag
8210
+ }
8211
+ },
8212
+ mcp: {
8213
+ description: `${SET_SHOWCASE_BEHAVIOR} Requires Moderate on the project, the same bar as the star on the card's Files list. Pass projectId to target a specific project; otherwise the configured default project is used.`,
8214
+ fields: {
8215
+ projectId: mcpProjectId,
8216
+ taskId: f.string({ desc: "The task ID or slug the file is attached to" }),
8217
+ fileId: f.string({ desc: "The file ID to change" }),
8218
+ showcase: showcaseFlag
8219
+ }
8220
+ }
8221
+ });
8177
8222
  var attachmentsContracts = [
8178
8223
  listTaskFilesContract,
8179
8224
  getAttachmentContract,
8180
8225
  uploadAttachmentContract,
8181
- deleteAttachmentContract
8226
+ deleteAttachmentContract,
8227
+ setShowcaseContract
8182
8228
  ];
8183
8229
  var createSuggestionContract = defineToolContract({
8184
8230
  name: "create_suggestion",
@@ -8501,7 +8547,7 @@ var createPullRequestContract = defineToolContract({
8501
8547
  fields: {
8502
8548
  title: f.string({ desc: "The PR title" }),
8503
8549
  body: f.string({
8504
- desc: "The PR description/body in markdown. If the diff changes anything a user sees rendered, the body MUST show the visual proof inline, not just point at the card: upload the capture with upload_attachment, then embed the downloadUrl it returns under a '## Screenshots' heading (![caption](url) for images, [caption](url) for video). Only image and video URLs belong in a PR body: a document downloadUrl (csv, pdf, md, \u2026) needs a signed-in project member and shows 404 to a reviewer on GitHub, so point at the card instead. Attach to the card as well \u2014 the card is the durable home, the PR body is what the reviewer reads."
8550
+ desc: "The PR description/body in markdown. If the diff changes anything a user sees rendered, the body MUST show the visual proof inline, not just point at the card: upload the capture with upload_attachment, then embed the downloadUrl it returns under a '## Screenshots' heading (![caption](url) for images, [caption](url) for video). Only image and video URLs belong in a PR body: a document downloadUrl (csv, pdf, md, \u2026) needs a signed-in project member and shows 404 to a reviewer on GitHub, so point at the card instead. Attach to the card as well \u2014 the card is the durable home, the PR body is what the reviewer reads. Showcase attachments (uploaded with `showcase: true`) are appended under '## Showcase' automatically when Conveyor creates the PR; mention them inline yourself only if you want them next to specific text."
8505
8551
  }),
8506
8552
  branch: f.optional(
8507
8553
  f.string({
@@ -8532,7 +8578,7 @@ var createPullRequestContract = defineToolContract({
8532
8578
  taskId: f.string({ desc: "The task ID whose branch should be opened as a PR" }),
8533
8579
  title: f.string({ desc: "Pull request title" }),
8534
8580
  body: f.string({
8535
- desc: "Pull request body (markdown). For a diff that changes rendered UI, embed the visual proof inline: upload the capture with upload_attachment and paste the downloadUrl it returns as ![caption](url), in addition to leaving it on the card."
8581
+ desc: "Pull request body (markdown). For a diff that changes rendered UI, embed the visual proof inline: upload the capture with upload_attachment and paste the downloadUrl it returns as ![caption](url), in addition to leaving it on the card. Showcase attachments (uploaded with `showcase: true`) are appended under '## Showcase' automatically when Conveyor creates the PR; mention them inline yourself only if you want them next to specific text."
8536
8582
  }),
8537
8583
  head: f.optional(
8538
8584
  f.string({ desc: "Source branch for the PR (defaults to the task's branch)" })
@@ -9840,7 +9886,7 @@ function buildCreatePullRequestTool(connection, config) {
9840
9886
  if (await hasUncommittedChanges(cwd)) {
9841
9887
  const message = commitMessage || `${title}
9842
9888
 
9843
- Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
9889
+ Co-Authored-By: Claude <noreply@anthropic.com>`;
9844
9890
  const commitHash = await stageAndCommit(cwd, message);
9845
9891
  if (commitHash) {
9846
9892
  connection.sendEvent({
@@ -10137,6 +10183,8 @@ var MIME_BY_EXT = {
10137
10183
  ".gz": "application/gzip",
10138
10184
  ".mp4": "video/mp4",
10139
10185
  ".webm": "video/webm",
10186
+ ".mov": "video/quicktime",
10187
+ ".m4v": "video/x-m4v",
10140
10188
  ".mp3": "audio/mpeg",
10141
10189
  ".wav": "audio/wav"
10142
10190
  };
@@ -10151,20 +10199,28 @@ function describeTags(applied, unknown) {
10151
10199
  }
10152
10200
  return parts.join("");
10153
10201
  }
10202
+ function describeSupersede(superseded, error) {
10203
+ if (superseded) {
10204
+ return ` Replaces ${superseded.fileName} (${superseded.fileId}), which now collapses under it as history.`;
10205
+ }
10206
+ if (error) return ` Did not replace the older file: ${error}.`;
10207
+ return "";
10208
+ }
10154
10209
  var EMBEDDABLE_IN_MARKDOWN = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
10155
- function describeEmbed(fileName, downloadUrl) {
10210
+ function describeEmbed(fileName, downloadUrl, showcase) {
10156
10211
  if (!downloadUrl) return "";
10157
10212
  const isImage = EMBEDDABLE_IN_MARKDOWN.has(extname(fileName).toLowerCase());
10213
+ const lead = showcase ? "Put this line under '## Showcase' in the PR body (the server adds it if you forget)" : "Paste this into the PR description";
10158
10214
  if (isImage) {
10159
10215
  return `
10160
10216
 
10161
- Paste this into the PR description to show it inline:
10217
+ ${lead} to show it inline:
10162
10218
  ![${fileName}](${downloadUrl})`;
10163
10219
  }
10164
10220
  if (isCapabilityServableMime(inferMimeType(fileName))) {
10165
10221
  return `
10166
10222
 
10167
- Paste this into the PR description to link it:
10223
+ ${lead} to link it:
10168
10224
  [${fileName}](${downloadUrl})`;
10169
10225
  }
10170
10226
  return `
@@ -10172,69 +10228,98 @@ Paste this into the PR description to link it:
10172
10228
  downloadUrl (signed-in project members only; agents read it with get_attachment): ${downloadUrl}`;
10173
10229
  }
10174
10230
  function buildUploadAttachmentTool(connection, config) {
10175
- return defineContractTool(uploadAttachmentContract, async ({ path, title, comment, tags }) => {
10176
- const caption = title ?? comment;
10177
- try {
10178
- const filePath = isAbsolute(path) ? path : join17(config.workspaceDir, path);
10179
- const mimeType = inferMimeType(filePath);
10180
- const info = await statWorkspacePath(filePath);
10181
- if (!info.isFile) {
10182
- return textResult(`File not found: ${filePath}`);
10183
- }
10184
- if (info.size > MAX_FILE_SIZE_BYTES) {
10231
+ return defineContractTool(
10232
+ uploadAttachmentContract,
10233
+ async ({ path, title, comment, tags, showcase, supersedes }) => {
10234
+ const caption = title ?? comment;
10235
+ try {
10236
+ const filePath = isAbsolute(path) ? path : join17(config.workspaceDir, path);
10237
+ const mimeType = inferMimeType(filePath);
10238
+ const info = await statWorkspacePath(filePath);
10239
+ if (!info.isFile) {
10240
+ return textResult(`File not found: ${filePath}`);
10241
+ }
10242
+ if (info.size > MAX_FILE_SIZE_BYTES) {
10243
+ return textResult(
10244
+ `File is ${info.size} bytes \u2014 exceeds the ${MAX_FILE_SIZE_BYTES} byte (25MB) upload limit.`
10245
+ );
10246
+ }
10247
+ const fileName = basename2(filePath);
10248
+ const { fileId, uploadUrl } = await connection.call("requestFileUpload", {
10249
+ sessionId: connection.sessionId,
10250
+ fileName,
10251
+ mimeType,
10252
+ fileSize: info.size
10253
+ });
10254
+ const bytes = await readWorkspaceBytes(filePath);
10255
+ const res = await fetch(uploadUrl, {
10256
+ method: "PUT",
10257
+ headers: { "Content-Type": mimeType },
10258
+ // Normalize to a plain Uint8Array: the workbench proxy hands back a
10259
+ // Buffer<ArrayBufferLike> that newer @types/node rejects as a BodyInit.
10260
+ body: new Uint8Array(bytes)
10261
+ });
10262
+ if (!res.ok) {
10263
+ return textResult(
10264
+ `Upload to storage failed: HTTP ${res.status} ${await res.text().catch(() => "")}`
10265
+ );
10266
+ }
10267
+ const result = await connection.call("confirmFileUpload", {
10268
+ sessionId: connection.sessionId,
10269
+ fileId,
10270
+ title: caption,
10271
+ tags,
10272
+ // Omitted when unset so the payload stays byte-identical for callers
10273
+ // that never heard of the flag.
10274
+ ...showcase === void 0 ? {} : { showcase },
10275
+ ...supersedes ? { supersedes } : {}
10276
+ });
10277
+ const isShowcase = result.showcase === true;
10185
10278
  return textResult(
10186
- `File is ${info.size} bytes \u2014 exceeds the ${MAX_FILE_SIZE_BYTES} byte (25MB) upload limit.`
10279
+ `${isShowcase ? "Showcase attachment. " : ""}Uploaded ${fileName} (${info.size} bytes) and posted it to the task chat${caption ? ` with caption "${caption}"` : ""}. File ID: ${result.fileId}${describeTags(result.appliedTags, result.unknownTags)}${describeSupersede(result.superseded, result.supersedeError)}${describeEmbed(fileName, result.downloadUrl, isShowcase)}`
10187
10280
  );
10188
- }
10189
- const fileName = basename2(filePath);
10190
- const { fileId, uploadUrl } = await connection.call("requestFileUpload", {
10191
- sessionId: connection.sessionId,
10192
- fileName,
10193
- mimeType,
10194
- fileSize: info.size
10195
- });
10196
- const bytes = await readWorkspaceBytes(filePath);
10197
- const res = await fetch(uploadUrl, {
10198
- method: "PUT",
10199
- headers: { "Content-Type": mimeType },
10200
- // Normalize to a plain Uint8Array: the workbench proxy hands back a
10201
- // Buffer<ArrayBufferLike> that newer @types/node rejects as a BodyInit.
10202
- body: new Uint8Array(bytes)
10203
- });
10204
- if (!res.ok) {
10281
+ } catch (error) {
10205
10282
  return textResult(
10206
- `Upload to storage failed: HTTP ${res.status} ${await res.text().catch(() => "")}`
10283
+ `Failed to upload attachment: ${error instanceof Error ? error.message : "Unknown error"}`
10207
10284
  );
10208
10285
  }
10209
- const result = await connection.call("confirmFileUpload", {
10286
+ }
10287
+ );
10288
+ }
10289
+ function buildDeleteAttachmentTool(connection) {
10290
+ return defineContractTool(deleteAttachmentContract, async ({ fileId }) => {
10291
+ try {
10292
+ const result = await connection.call("deleteTaskFile", {
10210
10293
  sessionId: connection.sessionId,
10211
- fileId,
10212
- title: caption,
10213
- tags
10294
+ fileId
10214
10295
  });
10215
10296
  return textResult(
10216
- `Uploaded ${fileName} (${info.size} bytes) and posted it to the task chat${caption ? ` with caption "${caption}"` : ""}. File ID: ${result.fileId}${describeTags(result.appliedTags, result.unknownTags)}${describeEmbed(fileName, result.downloadUrl)}`
10297
+ `Deleted ${result.fileName} (${result.id}) permanently. Any chat message that announced it keeps its text without the attachment.`
10217
10298
  );
10218
10299
  } catch (error) {
10219
10300
  return textResult(
10220
- `Failed to upload attachment: ${error instanceof Error ? error.message : "Unknown error"}`
10301
+ `Failed to delete attachment: ${error instanceof Error ? error.message : "Unknown error"}`
10221
10302
  );
10222
10303
  }
10223
10304
  });
10224
10305
  }
10225
- function buildDeleteAttachmentTool(connection) {
10226
- return defineContractTool(deleteAttachmentContract, async ({ fileId }) => {
10306
+ function buildSetShowcaseTool(connection) {
10307
+ return defineContractTool(setShowcaseContract, async ({ fileId, showcase }) => {
10227
10308
  try {
10228
- const result = await connection.call("deleteTaskFile", {
10309
+ const result = await connection.call("setTaskFileShowcase", {
10229
10310
  sessionId: connection.sessionId,
10230
- fileId
10311
+ fileId,
10312
+ showcase
10231
10313
  });
10314
+ if (!result.showcase) {
10315
+ return textResult(`${result.fileName} (${result.id}) is no longer a showcase file.`);
10316
+ }
10232
10317
  return textResult(
10233
- `Deleted ${result.fileName} (${result.id}) permanently. Any chat message that announced it keeps its text without the attachment.`
10318
+ `${result.fileName} (${result.id}) is now a showcase file. It is in the task chat and the server adds it to the PR body under '## Showcase'.${describeEmbed(result.fileName, result.downloadUrl, true)}`
10234
10319
  );
10235
10320
  } catch (error) {
10236
10321
  return textResult(
10237
- `Failed to delete attachment: ${error instanceof Error ? error.message : "Unknown error"}`
10322
+ `Failed to set showcase: ${error instanceof Error ? error.message : "Unknown error"}`
10238
10323
  );
10239
10324
  }
10240
10325
  });
@@ -10470,7 +10555,8 @@ function buildCommonTools(connection, config) {
10470
10555
  ...buildMutationTools(connection, config),
10471
10556
  buildUpdateIncidentTool(connection),
10472
10557
  buildUploadAttachmentTool(connection, config),
10473
- buildDeleteAttachmentTool(connection)
10558
+ buildDeleteAttachmentTool(connection),
10559
+ buildSetShowcaseTool(connection)
10474
10560
  ];
10475
10561
  }
10476
10562
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  WorkspaceCommandSupervisor,
8
8
  startWorkspaceCommandsAfterConnect,
9
9
  stopWorkspaceCommands
10
- } from "./chunk-WQC2YQT4.js";
10
+ } from "./chunk-G2CE3NFY.js";
11
11
  import {
12
12
  PtyHarness,
13
13
  SessionRunner,
@@ -27,7 +27,7 @@ import {
27
27
  runUsageProbe,
28
28
  sampleKeyUsage,
29
29
  usesNativeUsageReporting
30
- } from "./chunk-735H53UF.js";
30
+ } from "./chunk-SELXSOT7.js";
31
31
  import "./chunk-37J5MMQT.js";
32
32
  import {
33
33
  inheritedEnv,
@@ -46,7 +46,7 @@ import {
46
46
  createServiceLogger,
47
47
  fetchBootstrap,
48
48
  loadConveyorConfig
49
- } from "./chunk-B4YQABDZ.js";
49
+ } from "./chunk-CG7AHC25.js";
50
50
  import "./chunk-Q4FQOJ7D.js";
51
51
  import "./chunk-W4LZ7R6Z.js";
52
52
  import "./chunk-372R6E4C.js";
@@ -1468,7 +1468,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
1468
1468
  process.exit(1);
1469
1469
  }
1470
1470
  if (CONVEYOR_MODE === "serving") {
1471
- const { runServingSession } = await import("./serve-boot-KXWMUVAG.js");
1471
+ const { runServingSession } = await import("./serve-boot-YARBQBSA.js");
1472
1472
  exitContext.runnerMode = "serving";
1473
1473
  exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
1474
1474
  const outcome = await runServingSession({
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SessionRunner,
3
3
  unshallowRepo
4
- } from "./chunk-735H53UF.js";
4
+ } from "./chunk-SELXSOT7.js";
5
5
  import "./chunk-37J5MMQT.js";
6
6
  import "./chunk-EKTPDG3G.js";
7
7
  import {
@@ -17,7 +17,7 @@ import {
17
17
  stageAndCommit,
18
18
  updateRemoteToken,
19
19
  workspacePathExists
20
- } from "./chunk-B4YQABDZ.js";
20
+ } from "./chunk-CG7AHC25.js";
21
21
  import "./chunk-Q4FQOJ7D.js";
22
22
  import {
23
23
  runAuthTokenCommand,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  WorkspaceCommandSupervisor
3
- } from "./chunk-WQC2YQT4.js";
3
+ } from "./chunk-G2CE3NFY.js";
4
4
  import {
5
5
  AgentConnection,
6
6
  CodespacePortVisibility,
@@ -11,7 +11,7 @@ import {
11
11
  createServiceLogger,
12
12
  ensureOnTaskBranch,
13
13
  loadConveyorConfig
14
- } from "./chunk-B4YQABDZ.js";
14
+ } from "./chunk-CG7AHC25.js";
15
15
  import "./chunk-Q4FQOJ7D.js";
16
16
  import "./chunk-W4LZ7R6Z.js";
17
17
  import "./chunk-IA45XHOA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-agent",
3
- "version": "11.0.39",
3
+ "version": "11.0.41",
4
4
  "description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
5
5
  "keywords": [
6
6
  "agent",
@@ -38,7 +38,7 @@
38
38
  "typecheck": "tsgo --noEmit"
39
39
  },
40
40
  "dependencies": {
41
- "@anthropic-ai/claude-agent-sdk": "^0.3.283",
41
+ "@anthropic-ai/claude-agent-sdk": "^0.3.284",
42
42
  "@modelcontextprotocol/sdk": "^1.30.1",
43
43
  "node-pty": "^1.1.0",
44
44
  "socket.io-client": "^4.8.4",
@@ -204,7 +204,14 @@ runner: the pod can sleep mid-run and nothing will wake you.
204
204
  **A UI-visible change needs visual proof before the PR**, in both environments:
205
205
  capture a screenshot (static) or a short recording (interaction) with the host
206
206
  repo's own tooling and attach it with `mcp__conveyor__upload_attachment`, then
207
- embed the returned URL in the PR body. A UI PR without it is incomplete.
207
+ embed the returned URL in the PR body. A UI PR without it is incomplete. Pass
208
+ `showcase: true` for the capture that shows the finished result (the after
209
+ shot, the final recording); leave it off for before shots and scoping captures.
210
+ Showcase files post to chat, render first on the card, and land in the PR body
211
+ under `## Showcase`. A re-capture of something already on the card (a new cut
212
+ of the same recording) passes `supersedes: "<old fileId>"` from
213
+ `list_task_files`: the new file takes the old one's star and tags, and the old
214
+ one collapses under it as history instead of showing twice.
208
215
 
209
216
  **Manual tests are for human eyes, and few.** Before the PR, record with
210
217
  `mcp__conveyor__set_manual_tests` only what a person must see in the running
@@ -262,10 +269,12 @@ Instead: post the answer, config, or findings with
262
269
  `mcp__conveyor__upload_attachment` (any file type, up to 25MB), and complete
263
270
  the card directly with `force_update_task_status("Complete")` — there is no PR
264
271
  or review step for a no-code task. Never publish a deliverable as an off-card
265
- link; the card is where it belongs. When a revision supersedes an earlier
266
- upload, remove the old copy with `mcp__conveyor__delete_attachment` (permanent,
267
- `fileId` from `list_task_files`) so the Files list shows one current version
268
- of each deliverable.
272
+ link; the card is where it belongs. When a revision replaces an earlier
273
+ upload, upload it with `supersedes: "<old fileId>"` (the id from
274
+ `list_task_files`) so the card shows one current version of each deliverable
275
+ with the history collapsed under it. Reach for
276
+ `mcp__conveyor__delete_attachment` (permanent) only for a copy that should not
277
+ survive at all.
269
278
 
270
279
  When unsure, check the diff: a real diff means open a PR, no diff means finish
271
280
  in chat and mark it Complete.