@bivy/bivy 0.10.1-staging.301 → 0.10.1-staging.302

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/bin/bivy.mjs CHANGED
@@ -2232,16 +2232,18 @@ async function cmdSend(args = []) {
2232
2232
  process.exit(code);
2233
2233
  }
2234
2234
 
2235
- // `bivy attach <file> [--caption "…"] [--session <id>]` — surface a file the
2236
- // agent produced into the chat as an image/file attachment (the reverse of the
2237
- // composer paperclip). The universal path: any agent that can run a shell command
2238
- // can call this. The session id defaults to $BIVY_SESSION_ID, which every
2235
+ // `bivy attach <file> [--caption "…"] [--artifact] [--session <id>]` — surface a
2236
+ // file the agent produced into the chat as an image/file attachment (the reverse
2237
+ // of the composer paperclip). The universal path: any agent that can run a shell
2238
+ // command can call this. The session id defaults to $BIVY_SESSION_ID, which every
2239
2239
  // runtime adapter injects into the agent's subprocess env (see
2240
2240
  // src/runtime/session-env.ts) — except pi, whose SDK exposes its own
2241
2241
  // $PI_SESSION_ID instead (same id, different var name; see
2242
2242
  // resolveAttachSessionId). The file is resolved to an absolute path here (the
2243
2243
  // CLI's cwd is the agent's workdir) and confined to the session workspace
2244
- // server-side.
2244
+ // server-side. `--artifact` marks it as a named artifact (a durable output worth
2245
+ // surfacing in the session's Artifacts list) rather than an incidental inline
2246
+ // image — see packages/core/src/artifacts.ts.
2245
2247
  async function cmdAttach(args = []) {
2246
2248
  const flag = (name) => {
2247
2249
  const i = args.indexOf(name);
@@ -2251,6 +2253,7 @@ async function cmdAttach(args = []) {
2251
2253
  const caption = flag("--caption");
2252
2254
  const name = flag("--name");
2253
2255
  const mimeType = flag("--mime") || flag("--mimeType");
2256
+ const artifact = args.includes("--artifact");
2254
2257
  const flagsWithValue = new Set(["--session", "--caption", "--name", "--mime", "--mimeType"]);
2255
2258
  // First positional that isn't a flag or a flag's value.
2256
2259
  let file;
@@ -2261,7 +2264,7 @@ async function cmdAttach(args = []) {
2261
2264
  file = a;
2262
2265
  break;
2263
2266
  }
2264
- if (!file) { console.error(c.red('Usage: bivy attach <file> [--caption "…"] [--session <id>]')); process.exit(1); return; }
2267
+ if (!file) { console.error(c.red('Usage: bivy attach <file> [--caption "…"] [--artifact] [--session <id>]')); process.exit(1); return; }
2265
2268
  if (!sessionId) { console.error(c.red("No session id. Set --session <id> or run inside an agent session ($BIVY_SESSION_ID).")); process.exit(1); return; }
2266
2269
  const absPath = path.resolve(process.cwd(), file);
2267
2270
  if (!fs.existsSync(absPath)) { console.error(c.red(`File not found: ${file}`)); process.exit(1); return; }
@@ -2279,7 +2282,7 @@ async function cmdAttach(args = []) {
2279
2282
  res = await fetch(`${url(config)}/api/session/${encodeURIComponent(sessionId)}/attach`, {
2280
2283
  method: "POST",
2281
2284
  headers,
2282
- body: JSON.stringify({ path: absPath, caption, name, mimeType }),
2285
+ body: JSON.stringify({ path: absPath, caption, name, mimeType, artifact }),
2283
2286
  });
2284
2287
  } catch (error) {
2285
2288
  console.error(c.red(`Could not reach the Bivy node: ${error?.message || String(error)}`));
@@ -126,8 +126,13 @@ function buildAttachMcpServer(sdk, sessionId, attachToChat) {
126
126
  "cannot see files you only write to disk. The path must be inside the session workspace.", {
127
127
  filePath: z.string().describe("Path to the file, absolute or relative to the session workspace."),
128
128
  caption: z.string().optional().describe("Short caption shown next to the attachment in the chat."),
129
+ artifact: z
130
+ .boolean()
131
+ .optional()
132
+ .describe("Mark this as a named artifact — a durable output worth surfacing in the session's Artifacts list " +
133
+ "(a report, benchmark result, coverage output, or build archive) — rather than an incidental inline image."),
129
134
  }, async (args) => {
130
- const result = attachToChat(sessionId, { filePath: args.filePath, caption: args.caption });
135
+ const result = attachToChat(sessionId, { filePath: args.filePath, caption: args.caption, ...(args.artifact ? { artifact: true } : {}) });
131
136
  if ("error" in result)
132
137
  return { content: [{ type: "text", text: result.error }], isError: true };
133
138
  return {
@@ -39,6 +39,11 @@ export const BIVY_MCP_TOOLS = [
39
39
  properties: {
40
40
  path: { type: "string", description: "Path to the file to send, inside the session workspace (absolute, or relative to it)." },
41
41
  caption: { type: "string", description: "Optional short note shown with the attachment." },
42
+ artifact: {
43
+ type: "boolean",
44
+ description: "Mark this as a named artifact — a durable output worth surfacing in the session's Artifacts list " +
45
+ "(a report, benchmark result, coverage output, or build archive) — rather than an incidental inline image.",
46
+ },
42
47
  },
43
48
  required: ["path"],
44
49
  additionalProperties: false,
@@ -73,6 +78,7 @@ export async function runAttachToChat(endpoint, sessionId, args, fetchImpl, toke
73
78
  if (!filePath)
74
79
  return { isError: true, text: "Provide a `path` to a file inside the session workspace." };
75
80
  const caption = typeof args.caption === "string" ? args.caption : undefined;
81
+ const artifact = args.artifact === true;
76
82
  const base = endpoint.replace(/\/+$/, "");
77
83
  const headers = { "content-type": "application/json" };
78
84
  if (token)
@@ -82,7 +88,7 @@ export async function runAttachToChat(endpoint, sessionId, args, fetchImpl, toke
82
88
  res = await fetchImpl(`${base}/api/session/${encodeURIComponent(sessionId)}/attach`, {
83
89
  method: "POST",
84
90
  headers,
85
- body: JSON.stringify({ path: filePath, caption }),
91
+ body: JSON.stringify({ path: filePath, caption, ...(artifact ? { artifact } : {}) }),
86
92
  });
87
93
  }
88
94
  catch (error) {
@@ -309,7 +309,8 @@ export class IntegrationManager {
309
309
  if (!filePath)
310
310
  return { content: [{ type: "text", text: "filePath is required" }], details: {}, isError: true };
311
311
  const caption = typeof p.caption === "string" ? p.caption : undefined;
312
- const result = attachToChat(sessionId, { filePath, caption });
312
+ const artifact = p.artifact === true;
313
+ const result = attachToChat(sessionId, { filePath, caption, ...(artifact ? { artifact } : {}) });
313
314
  if ("error" in result)
314
315
  return { content: [{ type: "text", text: result.error }], details: {}, isError: true };
315
316
  return { content: [{ type: "text", text: `Attached ${result.ref.name} (${result.ref.kind}, ${result.ref.mimeType}) to the chat.` }], details: { ref: result.ref } };
@@ -37,6 +37,11 @@ export const ATTACH_TO_CHAT_TOOL = {
37
37
  parameters: Type.Object({
38
38
  filePath: Type.String({ description: "Path to the file, absolute or relative to the session workspace." }),
39
39
  caption: Type.Optional(Type.String({ description: "Short caption shown next to the attachment in the chat." })),
40
+ artifact: Type.Optional(Type.Boolean({
41
+ description: "Mark this as a named artifact — a durable output worth surfacing in the session's Artifacts list " +
42
+ "(a report, benchmark result, coverage output, or build archive) — rather than an incidental inline " +
43
+ "image. Leave unset for an ordinary screenshot or chart.",
44
+ })),
40
45
  }),
41
46
  };
42
47
  export const BUILT_IN_INTEGRATIONS = [
package/dist/server.js CHANGED
@@ -1138,11 +1138,12 @@ function recordAttachment(record, bytes, opts) {
1138
1138
  (record.seenAttachmentHashes ??= new Set()).add(ref.hash);
1139
1139
  const entryId = `att-${randomBytes(8).toString("hex")}`;
1140
1140
  const caption = opts.caption ? String(opts.caption).slice(0, 2000) : undefined;
1141
+ const artifact = Boolean(opts.artifact);
1141
1142
  // Anchor at the current base length so history replay interleaves the
1142
1143
  // attachment where it was emitted (see event-log outbound projection).
1143
1144
  const afterMessageCount = record.session.getMessages().length;
1144
- eventLog.appendOutboundAttachment(record.id, { afterMessageCount, id: entryId, ref, caption });
1145
- broadcast(stampSessionEvent({ type: "session.event", sessionId: record.id, event: { type: "attachment", id: entryId, ref, caption } }));
1145
+ eventLog.appendOutboundAttachment(record.id, { afterMessageCount, id: entryId, ref, caption, artifact });
1146
+ broadcast(stampSessionEvent({ type: "session.event", sessionId: record.id, event: { type: "attachment", id: entryId, ref, caption, ...(artifact ? { artifact } : {}) } }));
1146
1147
  return { ref };
1147
1148
  }
1148
1149
  /**
@@ -1161,7 +1162,7 @@ function attachToChat(record, opts) {
1161
1162
  });
1162
1163
  if (isAttachPlanError(plan))
1163
1164
  return { error: plan.error };
1164
- return recordAttachment(record, plan.bytes, { name: plan.name, mimeType: plan.mimeType, kind: plan.kind, caption: opts.caption });
1165
+ return recordAttachment(record, plan.bytes, { name: plan.name, mimeType: plan.mimeType, kind: plan.kind, caption: opts.caption, artifact: opts.artifact });
1165
1166
  }
1166
1167
  /** Extension guess for a passively-surfaced tool image, from its mime type. */
1167
1168
  function extFromImageMime(mimeType) {
@@ -10472,6 +10473,7 @@ app.post("/api/session/:id/attach", (req, res) => {
10472
10473
  caption: typeof req.body?.caption === "string" ? req.body.caption : undefined,
10473
10474
  mimeType: typeof req.body?.mimeType === "string" ? req.body.mimeType : undefined,
10474
10475
  name: typeof req.body?.name === "string" ? req.body.name : undefined,
10476
+ artifact: req.body?.artifact === true,
10475
10477
  });
10476
10478
  if ("error" in result)
10477
10479
  return res.status(400).json({ error: result.error });
@@ -221,7 +221,7 @@ export function replayOutboundAttachments(entries) {
221
221
  }
222
222
  return [...byId.values()].map((entry) => ({
223
223
  role: "assistant",
224
- content: [{ type: AGENT_ATTACHMENT_BLOCK, ref: entry.ref, caption: entry.caption }],
224
+ content: [{ type: AGENT_ATTACHMENT_BLOCK, ref: entry.ref, caption: entry.caption, ...(entry.artifact ? { artifact: true } : {}) }],
225
225
  afterMessageCount: entry.afterMessageCount,
226
226
  createdAt: entry.createdAt,
227
227
  id: entry.id,
@@ -451,6 +451,7 @@ export class EventLog {
451
451
  id: entry.id,
452
452
  ref: { ...entry.ref },
453
453
  ...(entry.caption ? { caption: entry.caption } : {}),
454
+ ...(entry.artifact ? { artifact: true } : {}),
454
455
  };
455
456
  this.enqueue(id, `oa:${entry.id}`, record);
456
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.10.1-staging.301",
3
+ "version": "0.10.1-staging.302",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",