@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 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(args.agent ? { agent: args.agent } : {}),
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
- finish(0);
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
- if (opts.title) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.16.14-staging.1",
3
+ "version": "0.16.14-staging.2",
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.",