@bivy/bivy 0.17.2-staging.3 → 0.17.2-staging.4

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.
@@ -134,3 +134,16 @@ export function mergeProviderCatalog(base, catalog) {
134
134
  }
135
135
  return [...byId.values()].sort((a, b) => a.name.localeCompare(b.name));
136
136
  }
137
+ /**
138
+ * Reply to a models.list query. A catalog read failure must never answer with
139
+ * silence: the client (notably a Cloud launch validating its saved model) could
140
+ * only time out without a cause. Name the real reason on the session instead.
141
+ */
142
+ export async function catalogReplyEvent(load, sessionId) {
143
+ try {
144
+ return await load();
145
+ }
146
+ catch (error) {
147
+ return { type: "session.error", sessionId, error: `Couldn't read this machine's model catalog: ${error instanceof Error ? error.message : String(error)}` };
148
+ }
149
+ }
package/dist/server.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import { createNodeUpdateChecker, updateRegistryUrl } from "./node-update.js";
5
5
  import { createRemoteSessionAdmission, RemoteSessionAdmissionError } from "./session/remote-session-admission.js";
6
+ import { requiresExistingSession, resolveTargetSession } from "./session/target-session.js";
6
7
  import path from "node:path";
7
8
  import os from "node:os";
8
9
  import { spawn, spawnSync } from "node:child_process";
@@ -33,7 +34,7 @@ import { AppService } from "./apps/service.js";
33
34
  import { createAppCommands } from "./controllers/app-commands.js";
34
35
  import { bindClientCommandRoutes } from "./http/client-command-routes.js";
35
36
  import { collectDiscoveredSessions, planNativeAdoption } from "./runtime/native-session-discovery.js";
36
- import { aggregateModelCatalog, mergeProviderCatalog } from "./runtime/model-catalog.js";
37
+ import { aggregateModelCatalog, catalogReplyEvent, mergeProviderCatalog } from "./runtime/model-catalog.js";
37
38
  import { RuntimeHost, enforcementLevelFor, remoteRuntimeEnabled } from "./runtime/host.js";
38
39
  import { RemoteRuntime, RemoteRuntimeSession } from "./runtime/remote.js";
39
40
  import { InMemorySessionLocationRegistry } from "./runtime/session-location.js";
@@ -2453,18 +2454,8 @@ const RELAY_COMMANDS = {
2453
2454
  // this a models.list answered for one agent (e.g. Codex's models) could
2454
2455
  // linger on the composer/picker after the user switched to another agent
2455
2456
  // (e.g. Claude) — the "Claude shows Codex models" bug.
2456
- let event;
2457
- try {
2458
- event = await modelsListEventFor(record);
2459
- }
2460
- catch (error) {
2461
- // A catalog read failure must never answer with silence: the generic
2462
- // relay catch would swallow the rejection and the client (notably a
2463
- // Cloud launch waiting to validate its saved model) could only guess
2464
- // via timeout. Name the real reason on the session instead.
2465
- relay?.sendEvent({ type: "session.error", sessionId: requestedSessionId ?? record.id, error: `Couldn't read this machine's model catalog: ${error instanceof Error ? error.message : String(error)}` });
2466
- return;
2467
- }
2457
+ // A catalog read failure answers with the real error, never silence.
2458
+ const event = await catalogReplyEvent(() => modelsListEventFor(record), requestedSessionId ?? record.id);
2468
2459
  relay?.sendEvent(event);
2469
2460
  },
2470
2461
  "models.prefetch"(msg) {
@@ -4915,31 +4906,13 @@ function assertSessionAccounts(record, labels) {
4915
4906
  }
4916
4907
  }
4917
4908
  async function continueCorrelatedSession(item, prompt, report, opts) {
4918
- if (item.targetKind !== "existing_session" || !item.targetSessionId)
4919
- return false;
4920
- let record = openSessions.get(item.targetSessionId);
4921
- if (!record && opts?.resumeOnMissing) {
4922
- // Same-node resume: reopen a closed session from its durable metadata +
4923
- // transcript — exactly how a prompt to an old session from the app is
4924
- // handled (resolveOrResumeSession). Best-effort: an unresolvable session
4925
- // falls through to the snapshot restore / fail handling below.
4926
- record = await resolveOrResumeSession(item.targetSessionId).catch(() => undefined);
4927
- }
4928
- if (!record) {
4929
- const restored = await restoreSessionFromSnapshot(item.targetSessionId);
4930
- if (restored)
4931
- record = await resolveOrResumeSession(item.targetSessionId).catch(() => undefined);
4932
- }
4933
- if (!record) {
4934
- // Strict mode (a scheduled message to a specific session): the session must
4935
- // be resumed in place, so a genuinely-unavailable one is surfaced as a
4936
- // failed run rather than silently starting a new session. Everything else
4937
- // keeps the established best-effort fall-through to a fresh pickup.
4938
- if (opts?.resumeOnMissing) {
4939
- throw new Error(`This Run could not continue session ${item.targetSessionId}: the session is not available on this Machine`);
4940
- }
4909
+ const record = await resolveTargetSession(item, {
4910
+ open: (id) => openSessions.get(id),
4911
+ resume: (id) => resolveOrResumeSession(id),
4912
+ restoreSnapshot: async (id) => Boolean(await restoreSessionFromSnapshot(id)),
4913
+ }, opts?.resumeOnMissing === true);
4914
+ if (!record)
4941
4915
  return false;
4942
- }
4943
4916
  assertSessionAccounts(record, opts?.credentialLabels);
4944
4917
  const branch = record.worktree?.branch;
4945
4918
  if (opts?.resumeOnMissing) {
@@ -5189,7 +5162,7 @@ async function executeWorkItem(item, report, signal) {
5189
5162
  // Scheduled runs targeting an existing session are STRICT: the message must
5190
5163
  // land in that session (resumed from disk if needed), never silently in a new
5191
5164
  // one — so a session that can't be resumed fails the run instead.
5192
- if (await continueCorrelatedSession(item, request, report, { resumeOnMissing: item.source === "schedule" || item.targetKind === "existing_session", isMessage, signal, credentialLabels }))
5165
+ if (await continueCorrelatedSession(item, request, report, { resumeOnMissing: requiresExistingSession(item), isMessage, signal, credentialLabels }))
5193
5166
  return;
5194
5167
  const requestedSandbox = normalizeSandboxTier(item.sandbox);
5195
5168
  // Prepare an explicit repository before resolving its policy. Otherwise a
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ //
4
+ // Resolving the Session a durable Run targets. A Run aimed at an existing
5
+ // Session (or any scheduled Run) is strict: it continues that Session, resumed
6
+ // from disk or a snapshot if needed, or fails visibly. It never silently cold-
7
+ // starts a new Session without the context the user pointed it at.
8
+ /** Scheduled Runs and Runs aimed at an existing Session must land in that Session. */
9
+ export function requiresExistingSession(item) {
10
+ return item.source === "schedule" || item.targetKind === "existing_session";
11
+ }
12
+ /**
13
+ * The targeted Session, or null when the item targets none or (non-strict) it is
14
+ * unavailable, in which case the caller falls through to a fresh pickup. Throws
15
+ * when a strict target cannot be continued.
16
+ */
17
+ export async function resolveTargetSession(item, resolvers, strict) {
18
+ if (item.targetKind !== "existing_session" || !item.targetSessionId)
19
+ return null;
20
+ const id = item.targetSessionId;
21
+ let record = resolvers.open(id);
22
+ if (!record && strict)
23
+ record = await resolvers.resume(id).catch(() => undefined);
24
+ if (!record && await resolvers.restoreSnapshot(id))
25
+ record = await resolvers.resume(id).catch(() => undefined);
26
+ if (record)
27
+ return record;
28
+ if (strict)
29
+ throw new Error(`This Run could not continue session ${id}: the session is not available on this Machine`);
30
+ return null;
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.17.2-staging.3",
3
+ "version": "0.17.2-staging.4",
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.",