@bivy/bivy 0.5.1-staging.71 → 0.5.1-staging.73
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/command-launch.js +12 -0
- package/dist/runtime/index.js +73 -15
- package/dist/server.js +21 -21
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-ALv2
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
/**
|
|
4
|
+
* Keep terminal semantics opt-in for short-lived commands. Native interactive
|
|
5
|
+
* commands must set requiresTty; ordinary commands stay on direct pipes and do
|
|
6
|
+
* not pay for a Python PTY relay.
|
|
7
|
+
*/
|
|
8
|
+
export function commandLaunch(command, args, requiresTty, pythonCommand, ptyRunnerScript) {
|
|
9
|
+
return requiresTty
|
|
10
|
+
? { command: pythonCommand, args: [ptyRunnerScript, command, ...args], usesPty: true }
|
|
11
|
+
: { command, args: [...args], usesPty: false };
|
|
12
|
+
}
|
package/dist/runtime/index.js
CHANGED
|
@@ -80,6 +80,7 @@ function claudeCodeInfo() {
|
|
|
80
80
|
const installed = claudeSdkInstalled();
|
|
81
81
|
return {
|
|
82
82
|
id: "claude-code-sdk",
|
|
83
|
+
executionMode: "protocol",
|
|
83
84
|
displayName: "Claude Code SDK",
|
|
84
85
|
description: "Anthropic's Claude Agent SDK driven as a Bivy runtime: streaming turns, model picker, and tool approvals via the SDK permission callback.",
|
|
85
86
|
status: installed ? "available" : "planned",
|
|
@@ -117,6 +118,7 @@ function genericCliInfo() {
|
|
|
117
118
|
const resume = Boolean(options?.resumeArgs);
|
|
118
119
|
return {
|
|
119
120
|
id: "generic-cli",
|
|
121
|
+
executionMode: "pipe",
|
|
120
122
|
displayName: process.env.BIVY_AGENT_NAME?.trim() || "Generic CLI Agent",
|
|
121
123
|
description: "Run any local agent CLI underneath Bivy by spawning a configured process and streaming stdout/stderr.",
|
|
122
124
|
status: configured ? "available" : "planned",
|
|
@@ -899,10 +901,23 @@ function cliAgentInfo(id) {
|
|
|
899
901
|
let resume = id === "codex" || Boolean(cliResumeTemplate(id));
|
|
900
902
|
let modelSelection = Boolean(cliModelConfig(id));
|
|
901
903
|
const usageReporting = cliUsageReporting(id);
|
|
904
|
+
const structuredPref = process.env.BIVY_AGENT_STRUCTURED;
|
|
905
|
+
const structuredAvailable = Boolean(process.env.BIVY_AGENT_PARSER || spec.parserId) && (!spec.parserUnverified || structuredPref === "1") && structuredPref !== "0";
|
|
902
906
|
// When the agent is promoted to ACP (spec.acp + BIVY_<ID>_ACP / BIVY_PREFER_ACP),
|
|
903
907
|
// it runs through the governed ProtocolRuntime — so it honestly gains per-tool
|
|
904
908
|
// approvals and resume. Reflect that in the catalog the picker reads.
|
|
905
909
|
const acpActive = prefersAcp(id);
|
|
910
|
+
// Catalog discovery must remain usable even when an operator has configured
|
|
911
|
+
// an invalid mode. The actual launch path resolves strictly and reports the
|
|
912
|
+
// actionable error; the picker falls back to the honest default here.
|
|
913
|
+
let executionMode;
|
|
914
|
+
try {
|
|
915
|
+
executionMode = resolveCliExecutionMode({ requested: requestedCliExecutionMode(id), protocolAvailable: Boolean(spec.acp), structuredAvailable, protocolPreferred: acpActive });
|
|
916
|
+
}
|
|
917
|
+
catch {
|
|
918
|
+
executionMode = structuredAvailable ? "structured-pipe" : "pipe";
|
|
919
|
+
}
|
|
920
|
+
const structured = executionMode === "structured-pipe";
|
|
906
921
|
if (acpActive)
|
|
907
922
|
resume = true;
|
|
908
923
|
// Opt-in self-healing: if the installed binary's --help doesn't evidence a
|
|
@@ -918,6 +933,7 @@ function cliAgentInfo(id) {
|
|
|
918
933
|
}
|
|
919
934
|
return {
|
|
920
935
|
id,
|
|
936
|
+
executionMode,
|
|
921
937
|
displayName: spec.displayName,
|
|
922
938
|
description: spec.blurb ?? `Run the local ${spec.displayName} CLI underneath Bivy in the session workspace.`,
|
|
923
939
|
status: installed ? "available" : "external",
|
|
@@ -950,6 +966,7 @@ function codexApprovalsInfo() {
|
|
|
950
966
|
const installed = commandAvailable("codex");
|
|
951
967
|
return {
|
|
952
968
|
id: "codex-approvals",
|
|
969
|
+
executionMode: "protocol",
|
|
953
970
|
displayName: "Codex",
|
|
954
971
|
description: "Codex driven through its app-server: every shell command or file change it proposes is gated through Bivy's Approve/Deny before it runs (not just the exec jail), and sessions resume with full history.",
|
|
955
972
|
status: installed ? "available" : "external",
|
|
@@ -1144,10 +1161,45 @@ function prefersAcp(id) {
|
|
|
1144
1161
|
return false;
|
|
1145
1162
|
return process.env.BIVY_PREFER_ACP === "1" || process.env[`BIVY_${id.toUpperCase()}_ACP`] === "1";
|
|
1146
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Resolve the communication mode for a CLI agent. This is deliberately pure so
|
|
1166
|
+
* it can be tested without starting a process. PTY is a terminal-launch mode,
|
|
1167
|
+
* not a governed ProcessRuntime mode; callers must handle it explicitly.
|
|
1168
|
+
*/
|
|
1169
|
+
export function resolveCliExecutionMode(input) {
|
|
1170
|
+
const raw = input.requested?.trim().toLowerCase() || "auto";
|
|
1171
|
+
const requested = (raw === "structured" ? "structured-pipe" : raw);
|
|
1172
|
+
if (!["auto", "protocol", "structured-pipe", "pipe", "pty"].includes(requested)) {
|
|
1173
|
+
throw new Error(`Invalid agent execution mode "${input.requested}". Use auto, protocol, structured-pipe, pipe, or pty.`);
|
|
1174
|
+
}
|
|
1175
|
+
if (requested === "pty")
|
|
1176
|
+
return "pty";
|
|
1177
|
+
if (requested === "protocol") {
|
|
1178
|
+
if (!input.protocolAvailable)
|
|
1179
|
+
throw new Error("Protocol execution was requested, but this agent has no configured protocol adapter.");
|
|
1180
|
+
return "protocol";
|
|
1181
|
+
}
|
|
1182
|
+
if (requested === "structured-pipe") {
|
|
1183
|
+
if (!input.structuredAvailable)
|
|
1184
|
+
throw new Error("Structured pipe execution was requested, but this agent has no available structured parser.");
|
|
1185
|
+
return "structured-pipe";
|
|
1186
|
+
}
|
|
1187
|
+
if (requested === "pipe")
|
|
1188
|
+
return "pipe";
|
|
1189
|
+
if (input.protocolAvailable && input.protocolPreferred)
|
|
1190
|
+
return "protocol";
|
|
1191
|
+
if (input.structuredAvailable)
|
|
1192
|
+
return "structured-pipe";
|
|
1193
|
+
return "pipe";
|
|
1194
|
+
}
|
|
1195
|
+
function requestedCliExecutionMode(id) {
|
|
1196
|
+
return process.env[`BIVY_${id.toUpperCase()}_MODE`] ?? process.env.BIVY_AGENT_MODE;
|
|
1197
|
+
}
|
|
1147
1198
|
function acpInfo() {
|
|
1148
1199
|
const configured = Boolean(process.env.BIVY_ACP_COMMAND?.trim());
|
|
1149
1200
|
return {
|
|
1150
1201
|
id: "acp",
|
|
1202
|
+
executionMode: "protocol",
|
|
1151
1203
|
displayName: process.env.BIVY_ACP_NAME?.trim() || "ACP Agent",
|
|
1152
1204
|
description: "Any Agent Client Protocol (ACP) agent, driven through Bivy's shim for per-tool approvals, streaming, and resume.",
|
|
1153
1205
|
status: configured ? "available" : "planned",
|
|
@@ -1202,6 +1254,7 @@ function openClawInfo() {
|
|
|
1202
1254
|
const installCommand = `npm install --global --prefix ${npmPrefix} openclaw`;
|
|
1203
1255
|
return {
|
|
1204
1256
|
id: "openclaw",
|
|
1257
|
+
executionMode: "pipe",
|
|
1205
1258
|
displayName: options.displayName,
|
|
1206
1259
|
description: "Run the local OpenClaw CLI underneath Bivy in the session workspace.",
|
|
1207
1260
|
status: installed ? "available" : "external",
|
|
@@ -1228,6 +1281,7 @@ function protocolInfo() {
|
|
|
1228
1281
|
const commands = protocolCommandsFromEnv();
|
|
1229
1282
|
return {
|
|
1230
1283
|
id: "bivy-agent-protocol",
|
|
1284
|
+
executionMode: "protocol",
|
|
1231
1285
|
displayName: process.env.BIVY_PROTOCOL_NAME?.trim() || "Bivy Protocol",
|
|
1232
1286
|
description: "JSON-lines process protocol for any agent to expose structured events, tool calls, approvals, models, and sessions without a bespoke Bivy adapter.",
|
|
1233
1287
|
status: configured ? "available" : "planned",
|
|
@@ -1244,6 +1298,7 @@ function protocolInfo() {
|
|
|
1244
1298
|
export const RUNTIME_CATALOG = [
|
|
1245
1299
|
{
|
|
1246
1300
|
id: "pi",
|
|
1301
|
+
executionMode: "protocol",
|
|
1247
1302
|
displayName: "Pi",
|
|
1248
1303
|
description: "Native Bivy/Pi coding agent runtime with packages, approvals, and model picker.",
|
|
1249
1304
|
status: "available",
|
|
@@ -1476,23 +1531,26 @@ function makeCliRuntime(id, options) {
|
|
|
1476
1531
|
const spec = CLI_AGENT_SPECS[id];
|
|
1477
1532
|
if (!commandAvailable(spec.command))
|
|
1478
1533
|
throw new Error(`${spec.displayName} command not found on PATH: ${spec.command}`);
|
|
1479
|
-
//
|
|
1480
|
-
//
|
|
1481
|
-
//
|
|
1482
|
-
|
|
1483
|
-
|
|
1534
|
+
// Resolve the mode before launching anything. ACP and structured parsing
|
|
1535
|
+
// remain data-driven; explicit mode overrides are fail-closed rather than
|
|
1536
|
+
// silently degrading to a less capable path.
|
|
1537
|
+
const structuredPref = process.env.BIVY_AGENT_STRUCTURED;
|
|
1538
|
+
const structuredAvailable = Boolean(process.env.BIVY_AGENT_PARSER || spec.parserId) && (!spec.parserUnverified || structuredPref === "1") && structuredPref !== "0";
|
|
1539
|
+
const executionMode = resolveCliExecutionMode({
|
|
1540
|
+
requested: requestedCliExecutionMode(id),
|
|
1541
|
+
protocolAvailable: Boolean(spec.acp),
|
|
1542
|
+
structuredAvailable,
|
|
1543
|
+
protocolPreferred: prefersAcp(id),
|
|
1544
|
+
});
|
|
1545
|
+
if (executionMode === "pty") {
|
|
1546
|
+
throw new Error(`PTY mode is for interactive terminal launches. Use 'bivy run ${spec.command}' instead of a governed chat session.`);
|
|
1547
|
+
}
|
|
1548
|
+
if (executionMode === "protocol") {
|
|
1549
|
+
if (!spec.acp)
|
|
1550
|
+
throw new Error(`${spec.displayName} does not declare an ACP/protocol launch mode.`);
|
|
1484
1551
|
return new ProtocolRuntime(acpRuntimeOptions({ id, displayName: spec.displayName, command: spec.command, agentArgs: spec.acp.args, credsDir: options.credsDir }));
|
|
1485
1552
|
}
|
|
1486
|
-
|
|
1487
|
-
// parser: launch with its native JSON flags and parse stdout into normalized
|
|
1488
|
-
// events. BIVY_AGENT_STRUCTURED=0 forces the dumb-pipe fallback everywhere;
|
|
1489
|
-
// BIVY_AGENT_STRUCTURED=1 opts INTO structured mode for agents whose parser
|
|
1490
|
-
// is still unverified (spec.parserUnverified — safe default is dumb pipe so a
|
|
1491
|
-
// wrong flag can't regress a working agent). BIVY_AGENT_PARSER overrides the
|
|
1492
|
-
// parser id (e.g. to "bivy-protocol").
|
|
1493
|
-
const structuredPref = process.env.BIVY_AGENT_STRUCTURED;
|
|
1494
|
-
const parserReady = Boolean(spec.parserId) && (!spec.parserUnverified || structuredPref === "1");
|
|
1495
|
-
const structured = parserReady && structuredPref !== "0";
|
|
1553
|
+
const structured = executionMode === "structured-pipe";
|
|
1496
1554
|
const parserId = process.env.BIVY_AGENT_PARSER || (structured ? spec.parserId : undefined);
|
|
1497
1555
|
const tier = sandboxTier(options.sandbox);
|
|
1498
1556
|
// BIVY_<ID>_ARGS overrides the launch flags for a CLI version we haven't
|
package/dist/server.js
CHANGED
|
@@ -46,6 +46,7 @@ import { buildSessionSnapshot, applySessionSnapshot } from "./session/snapshot.j
|
|
|
46
46
|
import { createCheckpointBundle, applyCheckpointBundle, materializeCheckpoint } from "./session/checkpoint-pack.js";
|
|
47
47
|
import { PolicyEngine } from "./policy/policy-engine.js";
|
|
48
48
|
import { TerminalManager } from "./terminal.js";
|
|
49
|
+
import { commandLaunch } from "./command-launch.js";
|
|
49
50
|
import { listMultiplexerSessions, attachCommand } from "./multiplexer.js";
|
|
50
51
|
import { createWorktree, removeWorktree, branchSlug, gitRepoRoot } from "./worktree.js";
|
|
51
52
|
import { HarnessManager } from "./harness/manager.js";
|
|
@@ -1415,12 +1416,12 @@ const commands = [
|
|
|
1415
1416
|
{ name: "/help", description: "Show quick chat help.", kind: "server", run: () => ({
|
|
1416
1417
|
text: "Use /commands to open the command list. Press Cmd/Ctrl+Enter to send a prompt. Attach files/images with the + button or by dragging them into the message box.",
|
|
1417
1418
|
}) },
|
|
1418
|
-
{ name: "/login", description: "Connect a model provider in Terminal.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "/login"] } },
|
|
1419
|
-
{ name: "/model", description: "Open the searchable model selector.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "/model"] } },
|
|
1420
|
-
{ name: "/terminal", description: "Start the terminal agent in this workspace and stream its output.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix] } },
|
|
1421
|
-
{ name: "/config", description: "Show agent configuration.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "config"] } },
|
|
1422
|
-
{ name: "/list", description: "List installed agent packages.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "list"] } },
|
|
1423
|
-
{ name: "/update", description: "Update agent packages.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "update"] } },
|
|
1419
|
+
{ name: "/login", description: "Connect a model provider in Terminal.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "/login"], requiresTty: true } },
|
|
1420
|
+
{ name: "/model", description: "Open the searchable model selector.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "/model"], requiresTty: true } },
|
|
1421
|
+
{ name: "/terminal", description: "Start the terminal agent in this workspace and stream its output.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix], requiresTty: true } },
|
|
1422
|
+
{ name: "/config", description: "Show agent configuration.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "config"], requiresTty: true } },
|
|
1423
|
+
{ name: "/list", description: "List installed agent packages.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "list"], requiresTty: true } },
|
|
1424
|
+
{ name: "/update", description: "Update agent packages.", kind: "native", spawn: { command: process.execPath, args: [...agentPrefix, "update"], requiresTty: true } },
|
|
1424
1425
|
];
|
|
1425
1426
|
// A local client whose send buffer has grown past this is behind on reads (slow
|
|
1426
1427
|
// network, background tab); used by both broadcast paths below.
|
|
@@ -5309,9 +5310,6 @@ function stripAnsi(text) {
|
|
|
5309
5310
|
.replace(/\r\n/g, "\n")
|
|
5310
5311
|
.replace(/\r/g, "\n");
|
|
5311
5312
|
}
|
|
5312
|
-
function wrapWithSystemPty(spawnCommand, args) {
|
|
5313
|
-
return { command: pythonCommand, args: [ptyRunnerScript, spawnCommand, ...args] };
|
|
5314
|
-
}
|
|
5315
5313
|
function shellQuote(value) {
|
|
5316
5314
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5317
5315
|
}
|
|
@@ -5433,18 +5431,20 @@ function runNativeCommand(command) {
|
|
|
5433
5431
|
throw new Error(`Command ${command.name} is not executable`);
|
|
5434
5432
|
const runId = `cmd-${Date.now()}`;
|
|
5435
5433
|
broadcast({ type: "command.started", runId, command: command.name });
|
|
5436
|
-
const
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5434
|
+
const env = {
|
|
5435
|
+
...process.env,
|
|
5436
|
+
PI_CODING_AGENT_DIR: piDir,
|
|
5437
|
+
BIVY_WORKSPACE: active?.workspace ?? defaultWorkspace,
|
|
5438
|
+
TERM: "xterm-256color",
|
|
5439
|
+
FORCE_COLOR: "0",
|
|
5440
|
+
NO_COLOR: "1",
|
|
5441
|
+
};
|
|
5442
|
+
// Native commands (login/model/config/etc.) need a TTY for prompts and the
|
|
5443
|
+
// terminal UI. Everything else uses ordinary pipes: this avoids an extra
|
|
5444
|
+
// Python process and PTY relay for non-interactive commands while preserving
|
|
5445
|
+
// the old behavior for commands that genuinely require terminal semantics.
|
|
5446
|
+
const launch = commandLaunch(command.spawn.command, command.spawn.args, command.spawn.requiresTty, pythonCommand, ptyRunnerScript);
|
|
5447
|
+
const child = spawn(launch.command, launch.args, { cwd: repoRoot, env });
|
|
5448
5448
|
commandProcesses.set(runId, child);
|
|
5449
5449
|
child.stdout.on("data", (data) => {
|
|
5450
5450
|
broadcast({ type: "command.output", runId, command: command.name, stream: "stdout", text: stripAnsi(String(data)) });
|
package/package.json
CHANGED