@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 +10 -7
- package/dist/agents/claude-code/runtime.js +6 -1
- package/dist/harness/mcp-serve-cli.js +7 -1
- package/dist/integrations/manager.js +2 -1
- package/dist/integrations/registry.js +5 -0
- package/dist/server.js +5 -3
- package/dist/session/event-log.js +2 -1
- package/package.json +1 -1
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
|
|
2236
|
-
// agent produced into the chat as an image/file attachment (the reverse
|
|
2237
|
-
// composer paperclip). The universal path: any agent that can run a shell
|
|
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
|
|
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