@tablation/crew 0.43.0 → 0.43.1

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
@@ -3184,6 +3184,7 @@ var DEFAULT_CONTRACT = {
3184
3184
  blockedBy: "blocked_by",
3185
3185
  updatedAt: "updated_at",
3186
3186
  needsDesign: "needs_design",
3187
+ attachments: "attachments",
3187
3188
  workMode: "work_mode",
3188
3189
  reportType: "report_type",
3189
3190
  epic: "epic_id",
@@ -4786,6 +4787,7 @@ var Tracker = class {
4786
4787
  * so concurrent first writes share one round of field-metadata reads.
4787
4788
  */
4788
4789
  authorship;
4790
+ attachments;
4789
4791
  stateDir;
4790
4792
  constructor(route, ship) {
4791
4793
  this.route = route;
@@ -5238,6 +5240,28 @@ var Tracker = class {
5238
5240
  shipId: shipRow?.id ?? null
5239
5241
  };
5240
5242
  }
5243
+ /**
5244
+ * The attachments probe (CREW-1558): which table the Issues `attachments`
5245
+ * column references, read once per process like the authorship probe. On
5246
+ * TABL-1557 two runs reported a ticket's screenshot as a 404 because, with
5247
+ * no table id in hand, they guessed `files/<attachment id>` paths; naming
5248
+ * the table in the Environment section is what makes the lookup mechanical.
5249
+ */
5250
+ attachmentsProbe() {
5251
+ this.attachments ??= this.probeAttachments();
5252
+ return this.attachments;
5253
+ }
5254
+ async probeAttachments() {
5255
+ try {
5256
+ const model = await this.client.dataModels.get(this.models.issues);
5257
+ const field = model.fields.find((f) => f.columnName === this.contract.columns.attachments);
5258
+ const fieldType = field?.fieldType;
5259
+ const target = fieldType?.targetModelId;
5260
+ return { mediaLibraryModelId: typeof target === "string" && target ? target : null };
5261
+ } catch {
5262
+ return { mediaLibraryModelId: null };
5263
+ }
5264
+ }
5241
5265
  async columnNames(modelId) {
5242
5266
  try {
5243
5267
  const model = await this.client.dataModels.get(modelId);
@@ -6874,6 +6898,7 @@ function renderEnvironment(i) {
6874
6898
  const only = repos.length === 1 ? repos[0] : void 0;
6875
6899
  const models = route.resolved?.models;
6876
6900
  const epicsModelId = route.resolved?.epicsModelId;
6901
+ const mediaLibraryModelId = i.attachments?.mediaLibraryModelId ?? null;
6877
6902
  const key = "ISSUE-000";
6878
6903
  const lines = [
6879
6904
  "# Your environment",
@@ -6898,7 +6923,8 @@ function renderEnvironment(i) {
6898
6923
  `- Issues: \`${models.issues}\``,
6899
6924
  `- Comments: \`${models.comments}\``,
6900
6925
  `- Crew: \`${models.crew}\``,
6901
- ...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : []
6926
+ ...epicsModelId ? [`- Epics: \`${epicsModelId}\``] : [],
6927
+ ...mediaLibraryModelId ? [`- Media Library: \`${mediaLibraryModelId}\` (what a ticket's \`${contract.columns.attachments}\` ids point into)`] : []
6902
6928
  );
6903
6929
  }
6904
6930
  const workspaceId = route.resolved?.workspaceId;
@@ -6925,6 +6951,30 @@ function renderEnvironment(i) {
6925
6951
  "status and response body in your comment \u2014 do not write that uploads are",
6926
6952
  "impossible."
6927
6953
  );
6954
+ if (mediaLibraryModelId) {
6955
+ lines.push(
6956
+ "",
6957
+ "## Opening a ticket's attachments",
6958
+ "",
6959
+ `A ticket's or a comment's \`${contract.columns.attachments}\` value is a list of Media Library`,
6960
+ "**record ids** \u2014 not file ids and not URLs. `files/<id>`, `files/<id>/download` and",
6961
+ "`media-library/<id>/...` do not exist for such an id and return 404. For each id,",
6962
+ "read the record, then fetch the file by its storage key:",
6963
+ "",
6964
+ "```sh",
6965
+ `curl -s -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
6966
+ ` "${base}/api/workspaces/${workspaceId}/data-models/${mediaLibraryModelId}/records/<attachment id>"`,
6967
+ '# \u2192 JSON with `storage_key` ("<uuid>/<filename>") and `mime_type`',
6968
+ `curl -s -o <scratch dir>/<filename> -H "Authorization: Bearer $${API_KEY_VAR}" -H "User-Agent: ${i.userAgent}" \\`,
6969
+ ` "${base}/api/workspaces/${workspaceId}/files/<storage_key>"`,
6970
+ "```",
6971
+ "",
6972
+ "The MCP forms are `records_controller_find_one` (`dataModelId` = the Media Library",
6973
+ "id above) and `files_controller_download` (`uuid` and `filename` = the two halves",
6974
+ "of the storage key). Save the file outside the repository and open it; where it",
6975
+ "disagrees with the description, the picture is the reproduction."
6976
+ );
6977
+ }
6928
6978
  }
6929
6979
  if (i.shipName) {
6930
6980
  lines.push(
@@ -11987,7 +12037,8 @@ async function buildEnvironment(route, ship, ticket) {
11987
12037
  };
11988
12038
  });
11989
12039
  const authorship = await tracker.authorshipProbe().catch(() => null);
11990
- return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, shipName: ship.name });
12040
+ const attachments = await tracker.attachmentsProbe().catch(() => null);
12041
+ return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name });
11991
12042
  }
11992
12043
 
11993
12044
  // src/cli.ts
@@ -12620,6 +12671,7 @@ ${changed2.map((c) => ` - ${c}
12620
12671
  contract,
12621
12672
  sourceTicket: ticket,
12622
12673
  authorship: await tracker.authorshipProbe().catch(() => null),
12674
+ attachments: await tracker.attachmentsProbe().catch(() => null),
12623
12675
  shipName: cfg.ship.name
12624
12676
  });
12625
12677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.43.0",
3
+ "version": "0.43.1",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,9 +47,12 @@ pass.
47
47
  builder's closing comment — what it changed, how it says it verified,
48
48
  what it admits it could not test — is the thing you are checking.
49
49
  Anything it skipped is your first test. **Open the ticket's own
50
- attachments first** (Step 3.4 in the shared policy says how): the
51
- reporter's screenshot is the repro you are judging the fix against. If
52
- you cannot open one, say so and set `needs_info` rather than passing it.
50
+ attachments first**, exactly as Step 3.4 in the shared policy says: read
51
+ the Media Library record for its `storage_key`, then fetch
52
+ `files/<storage_key>` — never a guessed `files/<id>` path. The reporter's
53
+ screenshot is the repro you are judging the fix against. If you cannot
54
+ open one, say so with the exact request and response and set `needs_info`
55
+ rather than passing it.
53
56
  4. **Get the branch and read the diff.** The branch on the remote is what
54
57
  you test, and you never need the builder's worktree: cut your own with
55
58
  `crew worktree <number>` (it prints the path), unless the builder's
@@ -284,12 +284,21 @@ before going further, and never change a ticket's `report_type` yourself.
284
284
  4. **Post a short comment** on what you are about to do (Comments table:
285
285
  `ticket_id`, `team_member_id` = your id, markdown `body`), and further
286
286
  comments at meaningful milestones — the operator wants visible progress,
287
- not just status flips. **Open the ticket's attachments before you start:**
288
- an `attachments` value is a list of Media Library record ids, usually the
289
- repro screenshot, and where it disagrees with the description the picture
290
- is the reproduction. If you cannot open one, that is a blocker (comment,
291
- `needs_info` plus `needs_planning`, stop). The `Ticket Attachments` skill
292
- has the resolution steps.
287
+ not just status flips. **Open the ticket's attachments before you start.**
288
+ An `attachments` value on the ticket or a comment is a list of Media
289
+ Library record ids — not file ids and not URLs, so no `files/<id>` path
290
+ will ever work. For each id: (1) read the Media Library record,
291
+ `GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
292
+ (the Environment section's "Opening a ticket's attachments" gives the
293
+ table id), and take its `storage_key`; (2) fetch the bytes with
294
+ `GET /api/workspaces/<workspaceId>/files/<storage_key>` (the MCP form is
295
+ `files_controller_download` with `uuid` and `filename` being the two
296
+ halves of the storage key) into a scratch directory and open it; (3)
297
+ compare it with the description — where they disagree, the picture is the
298
+ reproduction. If you cannot open one, that is a blocker (comment naming
299
+ it and the exact failure, `needs_info` plus `needs_planning`, stop); never
300
+ proceed on the prose alone. The `Ticket Attachments` skill has the edge
301
+ cases.
293
302
  5. **Implement the fix.** Read the relevant code first; do not guess at the
294
303
  architecture. **Never bump the version or edit the changelog on this
295
304
  branch** — the release phase assigns the version once per release, over
@@ -12,14 +12,20 @@ you picked up, and on any comment that has them, before you start.
12
12
 
13
13
  1. **Read the Media Library record for that id.** The `attachments` column is
14
14
  a reference into the workspace's Media Library table; its `storage_key`
15
- and `mime_type` are what you need. Take that table's id from the
16
- Environment section if it lists one, otherwise from a workspace-level
17
- model listing — never by reading the whole Issues model definition, which
18
- is over a megabyte.
19
- 2. **Fetch the bytes:** `GET /api/workspaces/<workspaceId>/files/<storage_key>`
20
- with the usual headers (the tracker MCP form is `files_controller_download`,
21
- which hands an image back as a viewable block). Save the file in a scratch
22
- directory outside the repository, then open it.
15
+ and `mime_type` are what you need:
16
+ `GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
17
+ (MCP: `records_controller_find_one`). The Environment section's "Opening
18
+ a ticket's attachments" names the table id; if it is absent, find the
19
+ table by name in a workspace-level model listing — never by reading the
20
+ whole Issues model definition, which is over a megabyte. The id is a
21
+ record id, not a file id: `files/<id>`, `files/<id>/download` and
22
+ `media-library/<id>/...` all 404 by design.
23
+ 2. **Fetch the bytes:** the `storage_key` has the form `<uuid>/<filename>`;
24
+ `GET /api/workspaces/<workspaceId>/files/<uuid>/<filename>` with the usual
25
+ headers streams it (the MCP form is `files_controller_download` with
26
+ `workspaceId`, `uuid` and `filename`, which hands an image back as a
27
+ viewable block). Save the file in a scratch directory outside the
28
+ repository, then open it.
23
29
  3. **Compare what the picture shows with what the description says.** Where
24
30
  they disagree, the picture is the reproduction.
25
31
 
@@ -46,9 +46,12 @@ pass.
46
46
  builder's closing comment — what it changed, how it says it verified,
47
47
  what it admits it could not test — is the thing you are checking.
48
48
  Anything it skipped is your first test. **Open the ticket's own
49
- attachments first** (Step 3.4 in the shared policy says how): the
50
- reporter's screenshot is the repro you are judging the fix against. If
51
- you cannot open one, say so and set `needs_info` rather than passing it.
49
+ attachments first**, exactly as Step 3.4 in the shared policy says: read
50
+ the Media Library record for its `storage_key`, then fetch
51
+ `files/<storage_key>` — never a guessed `files/<id>` path. The reporter's
52
+ screenshot is the repro you are judging the fix against. If you cannot
53
+ open one, say so with the exact request and response and set `needs_info`
54
+ rather than passing it.
52
55
  4. **Get the branch and read the diff.** The branch on the remote is what
53
56
  you test, and you never need the builder's worktree: cut your own with
54
57
  `crew worktree <number>` (it prints the path), unless the builder's
@@ -284,12 +284,21 @@ before going further, and never change a ticket's `report_type` yourself.
284
284
  4. **Post a short comment** on what you are about to do (Comments table:
285
285
  `ticket_id`, `team_member_id` = your id, markdown `body`), and further
286
286
  comments at meaningful milestones — the operator wants visible progress,
287
- not just status flips. **Open the ticket's attachments before you start:**
288
- an `attachments` value is a list of Media Library record ids, usually the
289
- repro screenshot, and where it disagrees with the description the picture
290
- is the reproduction. If you cannot open one, that is a blocker (comment,
291
- `needs_info` plus `needs_planning`, stop). The `Ticket Attachments` skill
292
- has the resolution steps.
287
+ not just status flips. **Open the ticket's attachments before you start.**
288
+ An `attachments` value on the ticket or a comment is a list of Media
289
+ Library record ids — not file ids and not URLs, so no `files/<id>` path
290
+ will ever work. For each id: (1) read the Media Library record,
291
+ `GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
292
+ (the Environment section's "Opening a ticket's attachments" gives the
293
+ table id), and take its `storage_key`; (2) fetch the bytes with
294
+ `GET /api/workspaces/<workspaceId>/files/<storage_key>` (the MCP form is
295
+ `files_controller_download` with `uuid` and `filename` being the two
296
+ halves of the storage key) into a scratch directory and open it; (3)
297
+ compare it with the description — where they disagree, the picture is the
298
+ reproduction. If you cannot open one, that is a blocker (comment naming
299
+ it and the exact failure, `needs_info` plus `needs_planning`, stop); never
300
+ proceed on the prose alone. The `Ticket Attachments` skill has the edge
301
+ cases.
293
302
  5. **Implement the fix.** Read the relevant code first; do not guess at the
294
303
  architecture. **Never bump the version or edit the changelog on this
295
304
  branch** — the release phase assigns the version once per release, over
@@ -12,14 +12,20 @@ you picked up, and on any comment that has them, before you start.
12
12
 
13
13
  1. **Read the Media Library record for that id.** The `attachments` column is
14
14
  a reference into the workspace's Media Library table; its `storage_key`
15
- and `mime_type` are what you need. Take that table's id from the
16
- Environment section if it lists one, otherwise from a workspace-level
17
- model listing — never by reading the whole Issues model definition, which
18
- is over a megabyte.
19
- 2. **Fetch the bytes:** `GET /api/workspaces/<workspaceId>/files/<storage_key>`
20
- with the usual headers (the tracker MCP form is `files_controller_download`,
21
- which hands an image back as a viewable block). Save the file in a scratch
22
- directory outside the repository, then open it.
15
+ and `mime_type` are what you need:
16
+ `GET /api/workspaces/<workspaceId>/data-models/<Media Library id>/records/<id>`
17
+ (MCP: `records_controller_find_one`). The Environment section's "Opening
18
+ a ticket's attachments" names the table id; if it is absent, find the
19
+ table by name in a workspace-level model listing — never by reading the
20
+ whole Issues model definition, which is over a megabyte. The id is a
21
+ record id, not a file id: `files/<id>`, `files/<id>/download` and
22
+ `media-library/<id>/...` all 404 by design.
23
+ 2. **Fetch the bytes:** the `storage_key` has the form `<uuid>/<filename>`;
24
+ `GET /api/workspaces/<workspaceId>/files/<uuid>/<filename>` with the usual
25
+ headers streams it (the MCP form is `files_controller_download` with
26
+ `workspaceId`, `uuid` and `filename`, which hands an image back as a
27
+ viewable block). Save the file in a scratch directory outside the
28
+ repository, then open it.
23
29
  3. **Compare what the picture shows with what the description says.** Where
24
30
  they disagree, the picture is the reproduction.
25
31