@bivy/bivy 0.4.0-staging.52 → 0.4.0-staging.54
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/attach-session-id.mjs +24 -0
- package/bin/bivy.mjs +9 -5
- package/dist/runtime/claude-code.js +23 -3
- package/dist/runtime/pi.js +13 -1
- package/dist/runtime/process.js +5 -1
- package/dist/runtime/protocol.js +5 -1
- package/dist/runtime/session-env.js +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-ALv2
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
// Pure session-id resolution for `bivy attach` (bin/bivy.mjs's cmdAttach),
|
|
4
|
+
// extracted so it's unit-testable without executing the CLI.
|
|
5
|
+
//
|
|
6
|
+
// Every runtime adapter injects BIVY_SESSION_ID into its agent's subprocess env
|
|
7
|
+
// (see src/runtime/session-env.ts) so `bivy attach <path>`, run from the agent's
|
|
8
|
+
// own shell, can resolve its session without being told the id. Pi
|
|
9
|
+
// (src/runtime/pi.ts) is the one exception: its agent loop runs in-process
|
|
10
|
+
// rather than under a subprocess Bivy controls, so it has no hook to inject
|
|
11
|
+
// BIVY_SESSION_ID into its own bash tool's env the way the other adapters do.
|
|
12
|
+
// The pi-coding-agent SDK's bash tool already exposes PI_SESSION_ID to every
|
|
13
|
+
// command it runs by default — and that id IS the Bivy session id for a pi
|
|
14
|
+
// session (PiSession.id reads the exact same SessionManager the SDK reads it
|
|
15
|
+
// from) — so it's accepted here as an equivalent fallback.
|
|
16
|
+
export function resolveAttachSessionId({ sessionFlag, env } = {}) {
|
|
17
|
+
const flag = typeof sessionFlag === "string" ? sessionFlag.trim() : "";
|
|
18
|
+
if (flag) return flag;
|
|
19
|
+
const bivy = env?.BIVY_SESSION_ID?.trim?.();
|
|
20
|
+
if (bivy) return bivy;
|
|
21
|
+
const pi = env?.PI_SESSION_ID?.trim?.();
|
|
22
|
+
if (pi) return pi;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
package/bin/bivy.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import { resolveSessionsLimit, truncateSavedSessions } from "./sessions-list.mjs
|
|
|
38
38
|
import { renderManagedBlock, upsertManagedBlock, removeManagedBlock, rcFileForShell } from "./shim-path.mjs";
|
|
39
39
|
import { removeExcept } from "./uninstall-paths.mjs";
|
|
40
40
|
import { findAvailablePort, reconcilePort } from "./port-picker.mjs";
|
|
41
|
+
import { resolveAttachSessionId } from "./attach-session-id.mjs";
|
|
41
42
|
|
|
42
43
|
const selfScript = fileURLToPath(import.meta.url);
|
|
43
44
|
const __dirname = path.dirname(selfScript);
|
|
@@ -1977,16 +1978,19 @@ async function cmdSend(args = []) {
|
|
|
1977
1978
|
// `bivy attach <file> [--caption "…"] [--session <id>]` — surface a file the
|
|
1978
1979
|
// agent produced into the chat as an image/file attachment (the reverse of the
|
|
1979
1980
|
// composer paperclip). The universal path: any agent that can run a shell command
|
|
1980
|
-
// can call this. The session id defaults to $BIVY_SESSION_ID, which
|
|
1981
|
-
// injects into the agent's subprocess env
|
|
1982
|
-
//
|
|
1983
|
-
//
|
|
1981
|
+
// can call this. The session id defaults to $BIVY_SESSION_ID, which every
|
|
1982
|
+
// runtime adapter injects into the agent's subprocess env (see
|
|
1983
|
+
// src/runtime/session-env.ts) — except pi, whose SDK exposes its own
|
|
1984
|
+
// $PI_SESSION_ID instead (same id, different var name; see
|
|
1985
|
+
// resolveAttachSessionId). The file is resolved to an absolute path here (the
|
|
1986
|
+
// CLI's cwd is the agent's workdir) and confined to the session workspace
|
|
1987
|
+
// server-side.
|
|
1984
1988
|
async function cmdAttach(args = []) {
|
|
1985
1989
|
const flag = (name) => {
|
|
1986
1990
|
const i = args.indexOf(name);
|
|
1987
1991
|
return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
|
|
1988
1992
|
};
|
|
1989
|
-
const sessionId = flag("--session")
|
|
1993
|
+
const sessionId = resolveAttachSessionId({ sessionFlag: flag("--session"), env: process.env });
|
|
1990
1994
|
const caption = flag("--caption");
|
|
1991
1995
|
const name = flag("--name");
|
|
1992
1996
|
const mimeType = flag("--mime") || flag("--mimeType");
|
|
@@ -26,6 +26,7 @@ import { sandboxTier, claudePermissionModeFor } from "../harness/sandbox.js";
|
|
|
26
26
|
import { anthropicCredentialPreflight, describeAnthropicError, isAnthropicAuthError } from "./anthropic-preflight.js";
|
|
27
27
|
import { toModelInfo as sharedToModelInfo } from "./normalize.js";
|
|
28
28
|
import { hasLiveProcessForCwd } from "./native-process-scan.js";
|
|
29
|
+
import { bivySessionEnv } from "./session-env.js";
|
|
29
30
|
/** Binary names a live Claude Code process could be running under (see
|
|
30
31
|
* native-process-scan.ts's best-effort cwd match). */
|
|
31
32
|
const CLAUDE_BIN_NAMES = ["claude"];
|
|
@@ -56,6 +57,21 @@ const FALLBACK_MODELS = [
|
|
|
56
57
|
{ provider: "anthropic", id: "claude-sonnet-5", name: "Claude Sonnet 5", reasoning: true },
|
|
57
58
|
{ provider: "anthropic", id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", reasoning: true },
|
|
58
59
|
];
|
|
60
|
+
/**
|
|
61
|
+
* Appended to the Claude Code system prompt so the agent DISCOVERS the outbound
|
|
62
|
+
* attachment capability. `bivy attach` is just a shell command — without this the
|
|
63
|
+
* agent has no way to know it exists and, when asked to "send a file", concludes
|
|
64
|
+
* it can't (it looks for a tool, finds none). BIVY_SESSION_ID is injected into the
|
|
65
|
+
* subprocess env (see spawnQuery), so the bare command resolves the session. Keep
|
|
66
|
+
* this short: it rides on every turn's system prompt.
|
|
67
|
+
*/
|
|
68
|
+
export const BIVY_ATTACH_SYSTEM_PROMPT = "Sending files and images to the user: the person you're talking to is in a chat UI. They cannot see files you only " +
|
|
69
|
+
"write to disk, and the chat cannot load remote image URLs or workspace file paths. " +
|
|
70
|
+
"To show them a file or image — a report, screenshot, chart, or a file they asked for — run " +
|
|
71
|
+
'`bivy attach <path> [--caption "short note"]` in your shell. ' +
|
|
72
|
+
"An image renders inline in the chat; any other file shows as a downloadable chip. The path must be inside the session " +
|
|
73
|
+
"workspace. Do NOT use markdown image syntax like  to show a local file or a URL — it will not render; always " +
|
|
74
|
+
"use `bivy attach`. Prefer this over pasting large file contents or describing where a file lives on disk.";
|
|
59
75
|
export function claudeRuntimeFromEnv() {
|
|
60
76
|
return {
|
|
61
77
|
defaultModel: process.env.BIVY_CLAUDE_MODEL?.trim() || undefined,
|
|
@@ -673,9 +689,9 @@ class ClaudeSession {
|
|
|
673
689
|
Object.assign(env, credEnv);
|
|
674
690
|
// Let the agent's own shell surface a file into the chat via `bivy attach`
|
|
675
691
|
// (POST /api/session/:id/attach). The session id is otherwise invisible to
|
|
676
|
-
// the subprocess.
|
|
677
|
-
//
|
|
678
|
-
env
|
|
692
|
+
// the subprocess. Shared with process.ts and protocol.ts via bivySessionEnv
|
|
693
|
+
// (see session-env.ts) so every CLI-spawning adapter injects it the same way.
|
|
694
|
+
Object.assign(env, bivySessionEnv(this.id));
|
|
679
695
|
this.spawnedToken = authTokenFromEnv(credEnv);
|
|
680
696
|
const options = {
|
|
681
697
|
cwd: this.cwd,
|
|
@@ -688,6 +704,10 @@ class ClaudeSession {
|
|
|
688
704
|
permissionMode,
|
|
689
705
|
canUseTool,
|
|
690
706
|
env,
|
|
707
|
+
// Keep the default Claude Code prompt, appending the note that teaches the
|
|
708
|
+
// agent how to send a file to the user (`bivy attach`) — otherwise the
|
|
709
|
+
// capability is undiscoverable and "send me X as an attachment" fails.
|
|
710
|
+
systemPrompt: { type: "preset", preset: "claude_code", append: BIVY_ATTACH_SYSTEM_PROMPT },
|
|
691
711
|
};
|
|
692
712
|
if (resumeId)
|
|
693
713
|
options.resume = resumeId;
|
package/dist/runtime/pi.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createPiModelRuntime } from "./pi-oauth.js";
|
|
|
17
17
|
import { toModelInfo as sharedToModelInfo } from "./normalize.js";
|
|
18
18
|
import { provisionPiAuthJson } from "./credential-provisioning.js";
|
|
19
19
|
import { isNativeOAuthProvider } from "./oauth/model-oauth-providers.js";
|
|
20
|
+
import { bivySessionEnv } from "./session-env.js";
|
|
20
21
|
/**
|
|
21
22
|
* Extract Pi's own slash commands from a live AgentSession: extension commands
|
|
22
23
|
* (`pi.registerCommand`, exposed via `extensionRunner.getRegisteredCommands()`),
|
|
@@ -175,6 +176,17 @@ class PiSession {
|
|
|
175
176
|
* daemon's own agent dir and session store (same files the SDK reads) and
|
|
176
177
|
* resumes by session file, so the TUI shows the live conversation. Returns
|
|
177
178
|
* null for an unsaved session (nothing to resume yet).
|
|
179
|
+
*
|
|
180
|
+
* This is also the one place PiSession spawns a subprocess Bivy itself
|
|
181
|
+
* configures, so it's where BIVY_SESSION_ID (see session-env.ts) is injected
|
|
182
|
+
* for this adapter. It does NOT cover the live-chat case (an agent turn
|
|
183
|
+
* running pi's own bash tool): pi's SDK runs its agent loop in-process and
|
|
184
|
+
* builds that tool's subprocess env internally, with no hook for a host to
|
|
185
|
+
* inject its own vars. That gap is closed differently — the SDK's bash tool
|
|
186
|
+
* already exposes PI_SESSION_ID to every command it runs, and that id IS the
|
|
187
|
+
* Bivy session id for a pi session (this.id reads the exact same
|
|
188
|
+
* SessionManager the SDK reads it from) — so `bivy attach` accepts
|
|
189
|
+
* PI_SESSION_ID as an equivalent fallback (see bin/attach-session-id.mjs).
|
|
178
190
|
*/
|
|
179
191
|
async interactiveTuiCommand() {
|
|
180
192
|
const file = this.sessionFile;
|
|
@@ -187,7 +199,7 @@ class PiSession {
|
|
|
187
199
|
return {
|
|
188
200
|
command: process.execPath,
|
|
189
201
|
args: [this.tui.piCli, "--session", file, "--session-dir", this.tui.sessionsDir],
|
|
190
|
-
env: { PI_CODING_AGENT_DIR: this.tui.piDir },
|
|
202
|
+
env: { PI_CODING_AGENT_DIR: this.tui.piDir, ...bivySessionEnv(this.id) },
|
|
191
203
|
};
|
|
192
204
|
}
|
|
193
205
|
prompt(text, options) {
|
package/dist/runtime/process.js
CHANGED
|
@@ -7,6 +7,7 @@ import { stripAnsi } from "./ansi.js";
|
|
|
7
7
|
import { buildAgentCredentialEnv } from "./credentials.js";
|
|
8
8
|
import { egressEnv } from "../harness/egress.js";
|
|
9
9
|
import { depCacheEnv } from "../harness/dep-cache.js";
|
|
10
|
+
import { bivySessionEnv } from "./session-env.js";
|
|
10
11
|
/**
|
|
11
12
|
* Send `signal` to `child`'s whole process group when possible, so a forking CLI
|
|
12
13
|
* agent's grandchildren (it shells out to git/npm/build tools, or forks its own
|
|
@@ -279,7 +280,10 @@ class ProcessSession {
|
|
|
279
280
|
cwd: this.cwd,
|
|
280
281
|
// egressEnv() routes this agent's outbound traffic through the harness
|
|
281
282
|
// network broker when BIVY_EGRESS_PROXY is enabled (else it's {}).
|
|
282
|
-
|
|
283
|
+
// bivySessionEnv() lets the agent's own shell resolve its session for
|
|
284
|
+
// `bivy attach <path>` (see session-env.ts); spread last so it can never
|
|
285
|
+
// be shadowed by an operator-configured env var of the same name.
|
|
286
|
+
env: { ...process.env, ...depCacheEnv(), ...this.runtimeOptions.env, ...credentialEnv, ...prepareEnv, ...egressEnv(), ...bivySessionEnv(this.id) },
|
|
283
287
|
stdio: "pipe",
|
|
284
288
|
// Detached so the child becomes the leader of its own process group
|
|
285
289
|
// (POSIX) — see killProcessGroup() / abort() below, which kill that whole
|
package/dist/runtime/protocol.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { EventEmitter } from "node:events";
|
|
6
6
|
import { buildAgentCredentialEnv } from "./credentials.js";
|
|
7
|
+
import { bivySessionEnv } from "./session-env.js";
|
|
7
8
|
import { extractTokenUsage } from "./cli-parsers.js";
|
|
8
9
|
/** A protocol `usage` message → UsageSnapshot (reuses the CLI token-key scan). */
|
|
9
10
|
function parseProtocolUsage(raw) {
|
|
@@ -258,7 +259,10 @@ class ProtocolSession {
|
|
|
258
259
|
: {};
|
|
259
260
|
const child = spawn(this.runtimeOptions.command, this.runtimeOptions.args ?? [], {
|
|
260
261
|
cwd: this.cwd,
|
|
261
|
-
|
|
262
|
+
// bivySessionEnv() lets the agent's own shell resolve its session for
|
|
263
|
+
// `bivy attach <path>` (see session-env.ts); spread last so it can never
|
|
264
|
+
// be shadowed by an operator-configured env var of the same name.
|
|
265
|
+
env: { ...process.env, ...this.runtimeOptions.env, ...credentialEnv, ...bivySessionEnv(this.id) },
|
|
262
266
|
stdio: "pipe",
|
|
263
267
|
});
|
|
264
268
|
this.child = child;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-ALv2
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
//
|
|
4
|
+
// Shared across every runtime adapter that spawns (or configures the spawn of) a
|
|
5
|
+
// subprocess for its agent: fold this into that subprocess's env so the agent's
|
|
6
|
+
// own shell can resolve its chat session without being told the id.
|
|
7
|
+
// `bivy attach <path>` (bin/bivy.mjs's cmdAttach) reads $BIVY_SESSION_ID to know
|
|
8
|
+
// which session to post an outbound attachment to — see
|
|
9
|
+
// claude-code.ts's BIVY_ATTACH_SYSTEM_PROMPT for the discoverability half of this
|
|
10
|
+
// feature (issue #288 shipped attach; issue #290 is making it universal). One
|
|
11
|
+
// helper, one env-var name, so a new adapter can't independently invent — or
|
|
12
|
+
// simply forget — its own convention.
|
|
13
|
+
//
|
|
14
|
+
// Used by claude-code.ts (spawnQuery), process.ts (ProcessSession.prompt), and
|
|
15
|
+
// protocol.ts (ProtocolSession.start). pi.ts is the one exception: Pi runs its
|
|
16
|
+
// agent loop in-process rather than spawning a subprocess Bivy controls, so it
|
|
17
|
+
// has no hook to inject this into its bash tool's env the same way — see the
|
|
18
|
+
// comment on PiSession.interactiveTuiCommand and bin/attach-session-id.mjs for
|
|
19
|
+
// how that gap is closed instead.
|
|
20
|
+
export function bivySessionEnv(sessionId) {
|
|
21
|
+
return { BIVY_SESSION_ID: sessionId };
|
|
22
|
+
}
|
package/package.json
CHANGED