@bivy/bivy 0.16.14-staging.1 → 0.16.14-staging.2
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 +1 -1
- package/dist/exec.js +55 -3
- package/dist/server.js +22 -7
- package/package.json +1 -1
package/bin/bivy.mjs
CHANGED
|
@@ -5040,7 +5040,7 @@ ${c.bold("bivy")} — Bivy node CLI
|
|
|
5040
5040
|
${c.cyan("bivy send <id>")} "..." Send a prompt to an existing session and stream the reply
|
|
5041
5041
|
${c.cyan("bivy kill <id>")} Stop a session/terminal (--delete also removes a saved session)
|
|
5042
5042
|
${c.cyan("bivy prune")} Delete old sessions/workspaces/worktrees (--keep N, --older-than 7d, --dry-run)
|
|
5043
|
-
${c.cyan("bivy exec")} "<prompt>" One-shot headless session: prints the answer to stdout (pipe-friendly)
|
|
5043
|
+
${c.cyan("bivy exec")} "<prompt>" One-shot headless session: prints the answer to stdout (pipe-friendly; --agent/--model/--name/--workspace/--session/--json)
|
|
5044
5044
|
${c.cyan("bivy runs start")} "<instructions>" Queue a one-off unattended Run with checks and a Receipt
|
|
5045
5045
|
${c.cyan("bivy automation")} list | trigger | init | validate | plan | test | apply
|
|
5046
5046
|
${c.cyan("bivy config")} init | validate | show | get | set | explain (typed node config)
|
package/dist/exec.js
CHANGED
|
@@ -7,15 +7,23 @@
|
|
|
7
7
|
//
|
|
8
8
|
// bivy exec "summarize README.md"
|
|
9
9
|
// bivy exec --agent claude "what does src/server.ts do?"
|
|
10
|
+
// bivy exec --workspace /path/to/repo --name "nightly audit" "…"
|
|
10
11
|
// bivy exec --session <ref> "and now add a test" # continue a session
|
|
11
12
|
// echo "explain this" | bivy exec - # prompt from stdin
|
|
13
|
+
//
|
|
14
|
+
// A turn that ends without any assistant text because the agent FAILED (e.g. an
|
|
15
|
+
// unauthenticated CLI) exits non-zero with the error on stderr, rather than
|
|
16
|
+
// reporting a silent empty success.
|
|
12
17
|
import { WebSocket } from "ws";
|
|
13
18
|
const err = (s) => process.stderr.write(s);
|
|
14
19
|
function parseArgs(argv) {
|
|
15
20
|
let url = process.env.BIVY_URL || `http://localhost:${process.env.PORT || "4317"}`;
|
|
16
21
|
let token = process.env.BIVY_DEVICE_TOKEN || undefined;
|
|
17
22
|
let agent;
|
|
23
|
+
let model;
|
|
18
24
|
let session;
|
|
25
|
+
let name;
|
|
26
|
+
let workspace;
|
|
19
27
|
let json = false;
|
|
20
28
|
let timeoutMs = Number(process.env.BIVY_EXEC_TIMEOUT_MS) || 10 * 60 * 1000;
|
|
21
29
|
const prompt = [];
|
|
@@ -33,10 +41,22 @@ function parseArgs(argv) {
|
|
|
33
41
|
agent = argv[++i];
|
|
34
42
|
else if (arg.startsWith("--agent="))
|
|
35
43
|
agent = arg.slice("--agent=".length);
|
|
44
|
+
else if ((arg === "-m" || arg === "--model") && argv[i + 1])
|
|
45
|
+
model = argv[++i];
|
|
46
|
+
else if (arg.startsWith("--model="))
|
|
47
|
+
model = arg.slice("--model=".length);
|
|
36
48
|
else if (arg === "--session" && argv[i + 1])
|
|
37
49
|
session = argv[++i];
|
|
38
50
|
else if (arg.startsWith("--session="))
|
|
39
51
|
session = arg.slice("--session=".length);
|
|
52
|
+
else if ((arg === "-n" || arg === "--name") && argv[i + 1])
|
|
53
|
+
name = argv[++i];
|
|
54
|
+
else if (arg.startsWith("--name="))
|
|
55
|
+
name = arg.slice("--name=".length);
|
|
56
|
+
else if ((arg === "-w" || arg === "--workspace") && argv[i + 1])
|
|
57
|
+
workspace = argv[++i];
|
|
58
|
+
else if (arg.startsWith("--workspace="))
|
|
59
|
+
workspace = arg.slice("--workspace=".length);
|
|
40
60
|
else if (arg === "--json")
|
|
41
61
|
json = true;
|
|
42
62
|
else if (arg === "--timeout" && argv[i + 1])
|
|
@@ -44,7 +64,7 @@ function parseArgs(argv) {
|
|
|
44
64
|
else
|
|
45
65
|
prompt.push(arg);
|
|
46
66
|
}
|
|
47
|
-
return { url: url.replace(/\/+$/, ""), token, agent, session, prompt: prompt.join(" "), json, timeoutMs };
|
|
67
|
+
return { url: url.replace(/\/+$/, ""), token, agent, model, session, name, workspace, prompt: prompt.join(" "), json, timeoutMs };
|
|
48
68
|
}
|
|
49
69
|
function authHeaders(token) {
|
|
50
70
|
return { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}) };
|
|
@@ -116,7 +136,17 @@ async function main() {
|
|
|
116
136
|
else {
|
|
117
137
|
const created = await api(args.url, args.token, "/api/session", {
|
|
118
138
|
method: "POST",
|
|
119
|
-
body: JSON.stringify(
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
...(args.agent ? { agent: args.agent } : {}),
|
|
141
|
+
// Mirror the `bivy-model:` directive path: an empty provider lets the
|
|
142
|
+
// server resolve the id against the session's catalog (the same
|
|
143
|
+
// setModel("", id) form). Passing the id verbatim keeps ids that embed
|
|
144
|
+
// a provider slash (e.g. `opencode/gpt-5.6-sol`) intact.
|
|
145
|
+
...(args.model ? { model: { provider: "", id: args.model } } : {}),
|
|
146
|
+
// Explicit, user-chosen name (kept verbatim; suppresses auto-naming).
|
|
147
|
+
...(args.name ? { name: args.name } : {}),
|
|
148
|
+
...(args.workspace ? { workspace: args.workspace } : {}),
|
|
149
|
+
}),
|
|
120
150
|
});
|
|
121
151
|
sessionId = created.id;
|
|
122
152
|
}
|
|
@@ -130,6 +160,9 @@ async function main() {
|
|
|
130
160
|
const socket = new WebSocket(wsUrl(args.url, args.token));
|
|
131
161
|
let answer = "";
|
|
132
162
|
let settled = false;
|
|
163
|
+
// Last stderr the agent surfaced (auth failures, crashes). Carried so a turn
|
|
164
|
+
// that ends with no assistant text can explain WHY instead of printing empty.
|
|
165
|
+
let lastStderr = "";
|
|
133
166
|
const finish = (code, errorText) => {
|
|
134
167
|
if (settled)
|
|
135
168
|
return;
|
|
@@ -192,9 +225,28 @@ async function main() {
|
|
|
192
225
|
if (text)
|
|
193
226
|
answer = text; // events carry the full message text so far
|
|
194
227
|
}
|
|
228
|
+
else if (ev.type === "tool_execution_update" && ev.toolName === "agent_output") {
|
|
229
|
+
const input = (ev.input || {});
|
|
230
|
+
if (input.stream === "stderr" && typeof input.output === "string" && input.output.trim()) {
|
|
231
|
+
lastStderr = input.output.trim();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
195
234
|
else if (ev.type === "agent_end") {
|
|
196
235
|
clearTimeout(timer);
|
|
197
|
-
|
|
236
|
+
// A turn can end without any assistant text because the agent FAILED
|
|
237
|
+
// (e.g. an unauthenticated CLI that printed to stderr and exited
|
|
238
|
+
// non-zero). Printing an empty answer with exit 0 would report a silent
|
|
239
|
+
// false success to a script. Surface the failure instead: prefer an
|
|
240
|
+
// explicit error string, then the last stderr line, then the exit code.
|
|
241
|
+
const errText = typeof ev.error === "string" && ev.error.trim() ? ev.error.trim() : undefined;
|
|
242
|
+
const code = typeof ev.code === "number" ? ev.code : undefined;
|
|
243
|
+
const failed = !!errText || (code !== undefined && code !== 0);
|
|
244
|
+
if (!answer.trim() && failed) {
|
|
245
|
+
finish(1, errText || lastStderr || `The agent produced no output (exit code ${code ?? "unknown"}).`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
finish(0);
|
|
249
|
+
}
|
|
198
250
|
}
|
|
199
251
|
}
|
|
200
252
|
else if (type === "session.error") {
|
package/dist/server.js
CHANGED
|
@@ -8372,10 +8372,7 @@ async function createWorkspaceSession(workspace, opts = {}) {
|
|
|
8372
8372
|
return createGitWorkspaceSession(workspace, parsed, opts);
|
|
8373
8373
|
}
|
|
8374
8374
|
const record = await createSession(workspace, undefined, { runtimeId: opts.runtimeId, sandbox: opts.sandbox, makeActive: opts.makeActive });
|
|
8375
|
-
|
|
8376
|
-
record.session.setName(`Session ${record.id.slice(0, 8)}`);
|
|
8377
|
-
persistSessionMetadata(record);
|
|
8378
|
-
}
|
|
8375
|
+
applyInitialSessionName(record, opts);
|
|
8379
8376
|
return record;
|
|
8380
8377
|
}
|
|
8381
8378
|
async function createGitWorkspaceSession(repoDir, parsed, opts = {}) {
|
|
@@ -8395,11 +8392,26 @@ async function createGitWorkspaceSession(repoDir, parsed, opts = {}) {
|
|
|
8395
8392
|
sandbox: opts.sandbox,
|
|
8396
8393
|
makeActive: opts.makeActive,
|
|
8397
8394
|
});
|
|
8395
|
+
applyInitialSessionName(record, opts);
|
|
8396
|
+
return record;
|
|
8397
|
+
}
|
|
8398
|
+
/**
|
|
8399
|
+
* Set a freshly created session's name before its first turn. An explicit,
|
|
8400
|
+
* user-chosen name (`opts.explicitName`) is kept verbatim so the first-turn
|
|
8401
|
+
* auto-namer leaves it alone; otherwise a raw first message (`opts.title`) only
|
|
8402
|
+
* pins a placeholder so that auto-namer can refine it from the message.
|
|
8403
|
+
*/
|
|
8404
|
+
function applyInitialSessionName(record, opts) {
|
|
8405
|
+
const explicit = opts.explicitName?.trim();
|
|
8406
|
+
if (explicit) {
|
|
8407
|
+
record.session.setName(explicit);
|
|
8408
|
+
persistSessionMetadata(record);
|
|
8409
|
+
return;
|
|
8410
|
+
}
|
|
8398
8411
|
if (opts.title) {
|
|
8399
8412
|
record.session.setName(`Session ${record.id.slice(0, 8)}`);
|
|
8400
8413
|
persistSessionMetadata(record);
|
|
8401
8414
|
}
|
|
8402
|
-
return record;
|
|
8403
8415
|
}
|
|
8404
8416
|
/**
|
|
8405
8417
|
* Resolve the GitHub repo a session's worktree branch belongs to, for both
|
|
@@ -10069,6 +10081,9 @@ app.post("/api/session", async (req, res, next) => {
|
|
|
10069
10081
|
// relay `session.new` repo path. Takes precedence over a manual workspace path.
|
|
10070
10082
|
const repoInput = typeof req.body?.repo === "string" ? req.body.repo.trim() : "";
|
|
10071
10083
|
const title = typeof req.body?.title === "string" ? req.body.title : undefined;
|
|
10084
|
+
// An explicit, user-chosen session name (e.g. `bivy exec --name`). Kept
|
|
10085
|
+
// verbatim, unlike `title` (a raw first message that only seeds auto-naming).
|
|
10086
|
+
const explicitName = typeof req.body?.name === "string" && req.body.name.trim() ? req.body.name.trim() : undefined;
|
|
10072
10087
|
const requestId = typeof req.body?.requestId === "string" ? req.body.requestId : undefined;
|
|
10073
10088
|
// Validate the workspace before entering the dedupe path so a bad path still
|
|
10074
10089
|
// returns a 400 (rather than being cached as a rejected creation).
|
|
@@ -10108,8 +10123,8 @@ app.post("/api/session", async (req, res, next) => {
|
|
|
10108
10123
|
// session this request already created rather than spawning a duplicate.
|
|
10109
10124
|
session = await dedupeSessionNew(requestId, async () => {
|
|
10110
10125
|
const rec = parsed
|
|
10111
|
-
? await createRepoSession(parsed, { title, runtimeId: agentFrom(req.body ?? {}), branch: branchFrom(req.body ?? {}) })
|
|
10112
|
-
: await createWorkspaceSession(workspace, { title, runtimeId: agentFrom(req.body ?? {}), branch: branchFrom(req.body ?? {}) });
|
|
10126
|
+
? await createRepoSession(parsed, { title, explicitName, runtimeId: agentFrom(req.body ?? {}), branch: branchFrom(req.body ?? {}) })
|
|
10127
|
+
: await createWorkspaceSession(workspace, { title, explicitName, runtimeId: agentFrom(req.body ?? {}), branch: branchFrom(req.body ?? {}) });
|
|
10113
10128
|
// Bind the composer's chosen model to the new session before its first turn.
|
|
10114
10129
|
await applyRequestedModel(rec, modelFrom(req.body ?? {}));
|
|
10115
10130
|
return rec;
|
package/package.json
CHANGED