@bivy/bivy 0.7.0-staging.96 → 0.7.0-staging.97

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/acp-shim.mjs CHANGED
@@ -57,45 +57,112 @@ function bivy(obj) {
57
57
  // --- ACP agent (child JSON-RPC over its stdio) ------------------------------
58
58
  const agent = spawn(agentCmd, agentArgs, { stdio: ["pipe", "pipe", "pipe"] });
59
59
  agent.stderr.on("data", (d) => process.stderr.write(`[acp-agent] ${d}`));
60
- agent.on("error", (e) => bivy({ type: "session.error", error: `acp agent spawn failed: ${e.message}` }));
61
- agent.on("exit", (code) => {
62
- if (code && code !== 0) bivy({ type: "session.error", error: `acp agent exited (${code})` });
63
- });
64
-
65
60
  let nextId = 1;
66
61
  const pending = new Map(); // jsonrpc id -> {resolve, reject}
67
- function agentRequest(method, params) {
62
+ let agentDead = null; // set to an Error once the child is gone
63
+
64
+ /**
65
+ * The child is gone (spawn failure or exit). Every in-flight request must be
66
+ * rejected: without this, a CLI whose ACP mode doesn't exist leaves `initialize`
67
+ * pending forever and the daemon waits out its whole session.create timeout instead
68
+ * of surfacing the real reason. Fail fast, with the reason.
69
+ */
70
+ function killPending(reason) {
71
+ if (agentDead) return;
72
+ agentDead = reason instanceof Error ? reason : new Error(String(reason));
73
+ bivy({ type: "session.error", error: agentDead.message });
74
+ for (const [id, p] of [...pending]) { pending.delete(id); p.reject(agentDead); }
75
+ }
76
+ agent.on("error", (e) => killPending(`acp agent spawn failed: ${e.message}`));
77
+ agent.on("exit", (code, signal) => {
78
+ if (code || signal) killPending(`acp agent exited (${code ?? signal})`);
79
+ });
80
+ // Writing to a dead child's stdin raises EPIPE; with no listener that's an uncaught
81
+ // exception that takes the shim down mid-turn instead of reporting the cause.
82
+ agent.stdin.on("error", (e) => killPending(`acp agent stdin closed: ${e.message}`));
83
+
84
+ function agentWrite(payload) {
85
+ if (agentDead) throw agentDead;
86
+ agent.stdin.write(`${JSON.stringify(payload)}\n`);
87
+ }
88
+ function agentRequest(method, params, { timeoutMs } = {}) {
68
89
  const id = nextId++;
69
- agent.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
70
- return new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
90
+ return new Promise((resolve, reject) => {
91
+ let timer;
92
+ pending.set(id, {
93
+ resolve: (v) => { clearTimeout(timer); resolve(v); },
94
+ reject: (e) => { clearTimeout(timer); reject(e); },
95
+ });
96
+ if (timeoutMs) {
97
+ timer = setTimeout(() => {
98
+ if (pending.delete(id)) reject(new Error(`acp ${method} timed out after ${timeoutMs}ms`));
99
+ }, timeoutMs);
100
+ }
101
+ try { agentWrite({ jsonrpc: "2.0", id, method, params }); }
102
+ catch (e) { clearTimeout(timer); pending.delete(id); reject(e); }
103
+ });
71
104
  }
72
105
  function agentReply(id, result) {
73
- agent.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`);
106
+ try { agentWrite({ jsonrpc: "2.0", id, result }); } catch { /* child gone; killPending already reported it */ }
74
107
  }
75
108
  function agentReplyError(id, code, message) {
76
- agent.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })}\n`);
109
+ try { agentWrite({ jsonrpc: "2.0", id, error: { code, message } }); } catch { /* child gone */ }
77
110
  }
78
111
  function agentNotify(method, params) {
79
- agent.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
112
+ try { agentWrite({ jsonrpc: "2.0", method, params }); } catch { /* child gone */ }
80
113
  }
81
114
 
82
115
  // --- session state ----------------------------------------------------------
83
116
  let sessionId = null;
84
117
  let cwd = process.cwd();
85
118
  let initialized = false;
119
+ // The ACP config-option id that selects the model (usually "model"), learned from
120
+ // session/new; used for the session/set_config_option fallback.
121
+ let modelConfigId = "model";
122
+ // A model chosen before the ACP session existed, applied once it does.
123
+ let pendingModel = null;
86
124
  // toolCallId -> { requestId, options } so a later bivy tool.decision answers the
87
125
  // right ACP permission request with a concrete optionId.
88
126
  const permissionRequests = new Map();
89
127
 
90
128
  async function ensureInitialized() {
91
129
  if (initialized) return;
130
+ // Bounded: a binary that accepts the launch args but never speaks ACP would
131
+ // otherwise hang here until the daemon's own session timeout, hiding the cause.
92
132
  await agentRequest("initialize", {
93
133
  protocolVersion: 1,
94
134
  clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
95
- });
135
+ }, { timeoutMs: 20_000 });
96
136
  initialized = true;
97
137
  }
98
138
 
139
+ /**
140
+ * ACP exposes a session's selectable models as a `select` config option on the
141
+ * session/new|load result (opencode: `configOptions: [{id:"model", currentValue,
142
+ * options:[{value,name}]}]`). Models are per-NODE — they depend on which providers
143
+ * the user has authenticated in the agent — so a hardcoded list would offer models
144
+ * the agent rejects. Publish what the agent actually reports, as a post-hello
145
+ * `runtime.models` event ProtocolRuntime folds into its picker.
146
+ */
147
+ function publishModels(result) {
148
+ const options = Array.isArray(result?.configOptions) ? result.configOptions : [];
149
+ const modelOption = options.find((o) => String(o?.id ?? "") === "model" || String(o?.category ?? "") === "model");
150
+ const choices = Array.isArray(modelOption?.options) ? modelOption.options : [];
151
+ const models = choices
152
+ .map((o) => ({ id: String(o?.value ?? ""), name: String(o?.name ?? o?.value ?? "") }))
153
+ .filter((m) => m.id)
154
+ // ACP model ids are `provider/model`; split the provider so Bivy can group and
155
+ // scope provider-specific settings the same way it does for other runtimes.
156
+ .map((m) => ({ ...m, provider: m.id.includes("/") ? m.id.split("/")[0] : "agent" }));
157
+ if (!models.length) return;
158
+ modelConfigId = String(modelOption?.id ?? "model");
159
+ bivy({
160
+ type: "runtime.models",
161
+ models,
162
+ ...(modelOption?.currentValue ? { currentModel: String(modelOption.currentValue) } : {}),
163
+ });
164
+ }
165
+
99
166
  // --- ACP → bivy: streamed session/update notifications ----------------------
100
167
  function onSessionUpdate(params) {
101
168
  const u = params?.update;
@@ -211,6 +278,33 @@ createInterface({ input: agent.stdout }).on("line", (line) => {
211
278
  if (msg.method === "session/update") onSessionUpdate(msg.params);
212
279
  });
213
280
 
281
+ /**
282
+ * Select a model on the live ACP session. `session/set_model` is the direct form;
283
+ * agents that only expose the generic config-option surface take the same choice as
284
+ * `session/set_config_option`. A rejection propagates: ProtocolRuntime only commits
285
+ * the selection once we ack, so a model the agent won't accept must not look applied.
286
+ */
287
+ async function setAgentModel(model) {
288
+ try {
289
+ await agentRequest("session/set_model", { sessionId, modelId: model }, { timeoutMs: 15_000 });
290
+ } catch (primary) {
291
+ try {
292
+ await agentRequest("session/set_config_option", { sessionId, configId: modelConfigId, value: model }, { timeoutMs: 15_000 });
293
+ } catch {
294
+ throw primary;
295
+ }
296
+ }
297
+ }
298
+
299
+ async function applyPendingModel() {
300
+ if (!pendingModel || !sessionId) return;
301
+ const model = pendingModel;
302
+ pendingModel = null;
303
+ // Best-effort: a stale pick shouldn't block the session from opening.
304
+ try { await setAgentModel(model); }
305
+ catch (e) { bivy({ type: "runtime.debug", message: `acp set_model failed: ${e instanceof Error ? e.message : String(e)}` }); }
306
+ }
307
+
214
308
  // --- bivy commands in (daemon → us) -----------------------------------------
215
309
  async function onBivyCommand(msg) {
216
310
  const type = String(msg.type || "");
@@ -224,6 +318,8 @@ async function onBivyCommand(msg) {
224
318
  cwd = String(msg.cwd || msg.workspace || cwd);
225
319
  const res = await agentRequest("session/new", { cwd, mcpServers: [] });
226
320
  sessionId = res?.sessionId ?? res?.session?.id ?? null;
321
+ publishModels(res);
322
+ await applyPendingModel();
227
323
  bivy({ replyTo: id, ok: true, runtimeSessionRef: sessionId });
228
324
  return;
229
325
  }
@@ -235,11 +331,14 @@ async function onBivyCommand(msg) {
235
331
  try {
236
332
  const res = await agentRequest("session/load", { sessionId: ref, cwd, mcpServers: [] });
237
333
  sessionId = res?.sessionId ?? ref;
334
+ publishModels(res);
238
335
  } catch {
239
336
  // Agent doesn't support session/load — start fresh so the chat still opens.
240
337
  const res = await agentRequest("session/new", { cwd, mcpServers: [] });
241
338
  sessionId = res?.sessionId ?? null;
339
+ publishModels(res);
242
340
  }
341
+ await applyPendingModel();
243
342
  bivy({ replyTo: id, ok: true, runtimeSessionRef: sessionId });
244
343
  return;
245
344
  }
@@ -270,6 +369,19 @@ async function onBivyCommand(msg) {
270
369
  }
271
370
  return;
272
371
  }
372
+ case "model.set": {
373
+ const model = String(msg.model ?? "").trim();
374
+ if (!model) { bivy({ replyTo: id, ok: true }); return; }
375
+ if (!sessionId) {
376
+ // Chosen before the session exists — remember and apply at session/new.
377
+ pendingModel = model;
378
+ bivy({ replyTo: id, ok: true });
379
+ return;
380
+ }
381
+ await setAgentModel(model);
382
+ bivy({ replyTo: id, ok: true });
383
+ return;
384
+ }
273
385
  case "session.abort": {
274
386
  if (sessionId) agentNotify("session/cancel", { sessionId });
275
387
  if (id !== undefined) bivy({ replyTo: id, ok: true });
@@ -286,8 +398,10 @@ async function onBivyCommand(msg) {
286
398
  }
287
399
 
288
400
  // Announce capabilities: ACP agents are governed (per-tool permission) and
289
- // resumable (session/load). Models aren't part of the core ACP surface, so we
290
- // don't advertise a picker we can't drive.
401
+ // resumable (session/load). modelSelection starts FALSE and is upgraded later by a
402
+ // `runtime.models` event if the session reports selectable models — the list is
403
+ // per-node (it depends on the providers the user has authenticated in the agent)
404
+ // and only arrives with session/new, so claiming a picker here would be a guess.
291
405
  bivy({ type: "hello", runtime: { capabilities: { toolInterception: true, modelSelection: false, resume: true } } });
292
406
 
293
407
  createInterface({ input: process.stdin }).on("line", (line) => {
@@ -20,8 +20,9 @@
20
20
  "label": "OpenCode",
21
21
  "command": "opencode",
22
22
  "hidden": false,
23
- "supportTier": "beta",
24
- "certification": "adapter-tested",
23
+ "supportTier": "supported",
24
+ "certification": "release-tested",
25
+ "testedVersion": "1.18.13",
25
26
  "headlessFlags": [
26
27
  "run",
27
28
  "-s"
@@ -179,7 +179,11 @@ const CLI_AGENT_SPECS = {
179
179
  // reply to stdout (the TUI needs a real TTY and would hang over a pipe).
180
180
  args: ["run"],
181
181
  promptMode: "argv",
182
- supportTier: "beta",
182
+ // Supported tier: OpenCode runs on the governed ACP path by default (per-tool
183
+ // Approve/Deny + session/load resume + a real model picker), the same bar Pi,
184
+ // Claude Code, and Codex clear. See `acp` below for the version fallback.
185
+ supportTier: "supported",
186
+ testedVersion: "1.18.13",
183
187
  blurb: "The most widely used open-source coding harness (OpenCode CLI).",
184
188
  // `opencode run -s <id> "<prompt>"` continues a prior session by its own id
185
189
  // (`-s, --session session id to continue`, per `opencode run --help`).
@@ -195,11 +199,14 @@ const CLI_AGENT_SPECS = {
195
199
  { id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro", provider: "google" },
196
200
  ],
197
201
  },
198
- // OpenCode ships a native ACP server (`opencode acp`, per opencode.ai/docs/acp),
199
- // so it can be driven through the governed ProtocolRuntime instead of the pipe
200
- // per-tool approvals + streaming + resume. Opt in with BIVY_OPENCODE_ACP=1 (or
201
- // global BIVY_PREFER_ACP=1); off by default until validated for your version.
202
- acp: { args: ["acp"] },
202
+ // `opencode acp` ("start ACP (Agent Client Protocol) server") drives OpenCode
203
+ // through the governed ProtocolRuntime instead of the one-shot pipe: per-tool
204
+ // Approve/Deny, streaming, `session/load` resume, and `session/set_model`.
205
+ // Validated against opencode 1.18.13, so it is ON by default (`preferred`)
206
+ // gated on the binary actually listing the `acp` subcommand, so an older
207
+ // OpenCode falls back to the pipe path rather than opening a dead session.
208
+ // Force the pipe path back with BIVY_OPENCODE_ACP=0.
209
+ acp: { args: ["acp"], helpToken: "acp", preferred: true },
203
210
  install: { kind: "npm", pkg: "opencode-ai" },
204
211
  },
205
212
  aider: {
@@ -857,10 +864,28 @@ function cliThinkingConfig(id) {
857
864
  // installed binary doesn't actually mention. It never UPGRADES — adding a
858
865
  // capability needs the exact arg template, which help text can't safely supply — so
859
866
  // probing can only make the catalog MORE honest, never invent a no-op control.
867
+ /**
868
+ * Absolute path of a command on the current PATH, or null when it isn't there.
869
+ * Used to key the help-probe cache: caching by the bare NAME would keep serving a
870
+ * stale answer after the binary behind that name changed (a CLI upgraded or
871
+ * installed while the daemon is running, or a different PATH entry winning).
872
+ */
873
+ function resolveCommandPath(command) {
874
+ if (!command.trim())
875
+ return null;
876
+ const res = spawnSync(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [command] : ["-v", command], {
877
+ shell: process.platform !== "win32",
878
+ encoding: "utf8",
879
+ });
880
+ if (res.status !== 0)
881
+ return null;
882
+ return (res.stdout ?? "").split(/\r?\n/)[0]?.trim() || null;
883
+ }
860
884
  const HELP_PROBE_CACHE = new Map();
861
885
  function probeHelpText(command) {
862
- if (HELP_PROBE_CACHE.has(command))
863
- return HELP_PROBE_CACHE.get(command) ?? null;
886
+ const key = resolveCommandPath(command) ?? command;
887
+ if (HELP_PROBE_CACHE.has(key))
888
+ return HELP_PROBE_CACHE.get(key) ?? null;
864
889
  let text = null;
865
890
  try {
866
891
  const res = spawnSync(command, ["--help"], { encoding: "utf8", timeout: 4000 });
@@ -870,7 +895,7 @@ function probeHelpText(command) {
870
895
  catch {
871
896
  text = null;
872
897
  }
873
- HELP_PROBE_CACHE.set(command, text);
898
+ HELP_PROBE_CACHE.set(key, text);
874
899
  return text;
875
900
  }
876
901
  // A resume template mixes launch flags (`-p`, `--force`) with the resume-specific
@@ -963,9 +988,14 @@ function cliAgentInfo(id) {
963
988
  // src/harness/mcp-inject.ts + governMcpCall in src/server.ts.
964
989
  capabilities: { toolInterception: acpActive, mcpToolApprovals: acpActive || Boolean(process.env.BIVY_MCP_PROXY), modelSelection, resume, packages: false, fork: false, usageReporting, sessionDiscovery: id === "codex" },
965
990
  supportTier: spec.supportTier ?? (id === "codex" ? "supported" : "experimental"),
991
+ testedVersion: spec.testedVersion,
966
992
  authOwner: spec.authOwner ?? "agent",
967
993
  notes: installed
968
- ? `Available on PATH. This process adapter ${spec.parserId && !spec.parserUnverified ? "parses its native JSON stream into a structured transcript" : spec.parserId ? "streams stdout/stderr (a structured JSON parser is available; opt in with BIVY_AGENT_STRUCTURED=1 once validated for your version)" : "streams stdout/stderr"}; Bivy governs its filesystem/exec/MCP effects at the sandbox tier rather than intercepting each tool call. Override its launch flags with BIVY_${id.toUpperCase()}_ARGS if your CLI version differs.`
994
+ ? acpActive
995
+ // Promoted to ACP: the description must match the governed path actually in
996
+ // use, not the pipe path this agent would otherwise take.
997
+ ? `Available on PATH, driven through its Agent Client Protocol server (\`${spec.command} ${spec.acp?.args.join(" ")}\`): each tool call is gated by Bivy's Approve/Deny before it runs, and sessions resume natively. Force the plain stdout pipe with BIVY_${id.toUpperCase()}_ACP=0.`
998
+ : `Available on PATH. This process adapter ${spec.parserId && !spec.parserUnverified ? "parses its native JSON stream into a structured transcript" : spec.parserId ? "streams stdout/stderr (a structured JSON parser is available; opt in with BIVY_AGENT_STRUCTURED=1 once validated for your version)" : "streams stdout/stderr"}; Bivy governs its filesystem/exec/MCP effects at the sandbox tier rather than intercepting each tool call. Override its launch flags with BIVY_${id.toUpperCase()}_ARGS if your CLI version differs.`
969
999
  : `${spec.command} was not found on PATH. Install it on this node, then select this agent again.`,
970
1000
  install: installed || !installCommand ? undefined : {
971
1001
  label: `Install ${spec.displayName}`,
@@ -980,6 +1010,13 @@ function cliAgentInfo(id) {
980
1010
  // Approve/Deny card via guardianInterceptor, AND it resumes a prior thread by its
981
1011
  // rollout id (thread/resume). Governed + resumable in one runtime supersedes the
982
1012
  // exec path, which stays runnable via `BIVY_RUNTIME=codex` for a no-approval flow.
1013
+ /**
1014
+ * The Codex CLI release this adapter was last certified against. Unlike Pi and the
1015
+ * Claude Agent SDK, Codex is an external binary rather than a pinned npm dependency,
1016
+ * so there is no lockfile entry to derive this from — it is bumped deliberately when
1017
+ * the app-server shim is re-validated against a new Codex release.
1018
+ */
1019
+ const CODEX_TESTED_VERSION = "0.145.0";
983
1020
  function codexApprovalsInfo() {
984
1021
  const installed = commandAvailable("codex");
985
1022
  return {
@@ -1009,7 +1046,12 @@ function codexApprovalsInfo() {
1009
1046
  nativeSessionDiscovery: true,
1010
1047
  nativeSessionAdoption: true,
1011
1048
  },
1012
- supportTier: "beta",
1049
+ // Supported tier: the app-server shim already clears the same bar as Pi and
1050
+ // Claude Code — per-tool Approve/Deny, model selection, thread resume, usage
1051
+ // reporting, and native session discovery/adoption — all over a bidirectional
1052
+ // protocol rather than a one-shot pipe.
1053
+ supportTier: "supported",
1054
+ testedVersion: CODEX_TESTED_VERSION,
1013
1055
  authOwner: "agent",
1014
1056
  notes: installed
1015
1057
  ? "Drives Codex's experimental app-server so tool calls surface as in-chat approval cards, and resumes a prior thread by its rollout id (thread/resume). Governance AND resume in one runtime."
@@ -1202,15 +1244,50 @@ function acpRuntimeFromEnv(credsDir) {
1202
1244
  return acpRuntimeOptions({ id: "acp", displayName: process.env.BIVY_ACP_NAME?.trim() || "ACP Agent", command, agentArgs, credsDir });
1203
1245
  }
1204
1246
  /**
1205
- * Whether a CLI agent should be driven through ACP rather than the one-shot pipe:
1206
- * it declares an `acp` mode AND ACP is preferred for it (per-agent `BIVY_<ID>_ACP=1`
1207
- * or global `BIVY_PREFER_ACP=1`). This is the data-driven "promote an agent to the
1208
- * high-capability path" switch no per-agent code, just a spec field + a flag.
1247
+ * Does the INSTALLED binary actually evidence the agent's ACP mode? A default-on
1248
+ * promotion must never be taken on faith: ACP is a hard switch (the pipe path is
1249
+ * unreachable once a session opens), so a CLI too old to have the subcommand would
1250
+ * otherwise hang and die instead of degrading. We reuse the same cached `--help`
1251
+ * probe the opt-in capability refinement uses, and fail CLOSED — a missing binary
1252
+ * or unreadable help keeps the agent on the honest pipe path.
1253
+ */
1254
+ function acpSupportedByBinary(id) {
1255
+ const spec = CLI_AGENT_SPECS[id];
1256
+ if (!spec.acp)
1257
+ return false;
1258
+ if (!commandAvailable(spec.command))
1259
+ return false;
1260
+ const help = probeHelpText(spec.command);
1261
+ if (!help)
1262
+ return false;
1263
+ const token = (spec.acp.helpToken ?? spec.acp.args[0] ?? "acp").toLowerCase();
1264
+ return help.includes(token);
1265
+ }
1266
+ /**
1267
+ * Whether a CLI agent should be driven through ACP rather than the one-shot pipe.
1268
+ * Three ways in, in precedence order:
1269
+ * - `BIVY_<ID>_ACP=0` — operator forces the pipe path back (escape hatch).
1270
+ * - `BIVY_<ID>_ACP=1` / `BIVY_PREFER_ACP=1` — operator forces ACP, no probe (they
1271
+ * know their binary; an explicit request shouldn't be second-guessed).
1272
+ * - `spec.acp.preferred` — validated agents are promoted by DEFAULT, but only
1273
+ * when the installed binary evidences the ACP mode (see acpSupportedByBinary).
1274
+ * Still no per-agent code: a spec field plus a flag.
1275
+ *
1276
+ * Both the catalog (cliAgentInfo) and the launch path (makeCliRuntime) call this,
1277
+ * so what the picker advertises and what actually starts cannot disagree.
1209
1278
  */
1210
1279
  function prefersAcp(id) {
1211
- if (!CLI_AGENT_SPECS[id].acp)
1280
+ const spec = CLI_AGENT_SPECS[id];
1281
+ if (!spec.acp)
1282
+ return false;
1283
+ const override = process.env[`BIVY_${id.toUpperCase()}_ACP`];
1284
+ if (override === "0")
1285
+ return false;
1286
+ if (override === "1" || process.env.BIVY_PREFER_ACP === "1")
1287
+ return true;
1288
+ if (!spec.acp.preferred)
1212
1289
  return false;
1213
- return process.env.BIVY_PREFER_ACP === "1" || process.env[`BIVY_${id.toUpperCase()}_ACP`] === "1";
1290
+ return acpSupportedByBinary(id);
1214
1291
  }
1215
1292
  /**
1216
1293
  * Resolve the communication mode for a CLI agent. This is deliberately pure so
@@ -410,6 +410,21 @@ class ProtocolSession {
410
410
  this.runtimeSessionRef = msg.runtimeSessionRef;
411
411
  return;
412
412
  }
413
+ // Late-arriving model registry. A shim that knows its models up front puts them
414
+ // in `hello`; one whose list is only knowable per session — an ACP agent's
415
+ // models depend on which providers the user has authenticated, and arrive with
416
+ // session/new — publishes them here instead. Same contract as the hello path: a
417
+ // picker backed by a real `model.set` the shim answers, never a claimed one.
418
+ if (type === "runtime.models") {
419
+ const models = parseModels(msg.models);
420
+ if (models.length) {
421
+ this.models = models;
422
+ this.capabilitiesRef.modelSelection = true;
423
+ if (typeof msg.currentModel === "string")
424
+ this.currentModelId = msg.currentModel;
425
+ }
426
+ return;
427
+ }
413
428
  if (type === "message.delta") {
414
429
  const text = String(msg.text ?? "");
415
430
  if (!this.assistantText)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.7.0-staging.96",
3
+ "version": "0.7.0-staging.97",
4
4
  "type": "module",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "description": "Run coding agents on machines you own. Source-available, self-hostable agent workspace.",