@bivy/bivy 0.16.25-staging.9 → 0.17.0-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.
Files changed (44) hide show
  1. package/README.md +4 -0
  2. package/bin/acp-shim.mjs +7 -0
  3. package/bin/agent-manifest.json +2 -2
  4. package/bin/bivy.mjs +55 -0
  5. package/bin/codex-app-server-shim.mjs +10 -0
  6. package/bin/patch-pi-dependencies.mjs +2 -2
  7. package/dist/agents/claude-code/integration.js +9 -1
  8. package/dist/agents/codex/integration.js +9 -1
  9. package/dist/agents/pi/integration.js +1 -1
  10. package/dist/agents/pi/runtime.js +27 -3
  11. package/dist/agents/profiles.js +27 -6
  12. package/dist/apps/gateway.js +357 -0
  13. package/dist/apps/preview-shell.js +55 -0
  14. package/dist/apps/preview-stream.js +58 -0
  15. package/dist/apps/registry.js +160 -0
  16. package/dist/apps/remote-preview.js +70 -0
  17. package/dist/apps/service.js +62 -0
  18. package/dist/apps/styles.css +3165 -0
  19. package/dist/apps/tokens.css +203 -0
  20. package/dist/apps/types.js +9 -0
  21. package/dist/automation-cli.js +26 -3
  22. package/dist/automation-config.js +14 -2
  23. package/dist/automation-filter-gate.js +32 -0
  24. package/dist/automation-filter.js +118 -0
  25. package/dist/automation-template.js +18 -4
  26. package/dist/certification/generated.js +5 -4
  27. package/dist/control-plane-tasks.js +14 -0
  28. package/dist/controllers/app-commands.js +28 -0
  29. package/dist/controllers/credential-commands.js +8 -3
  30. package/dist/credentials/document.js +35 -10
  31. package/dist/credentials/store.js +17 -0
  32. package/dist/hosted-custody.js +30 -0
  33. package/dist/protocol/client-command-routes.js +1 -0
  34. package/dist/protocol/client-command-schemas.js +4 -0
  35. package/dist/remote/relay-client.js +14 -0
  36. package/dist/runtime/cli-parsers.js +29 -2
  37. package/dist/runtime/index.js +43 -1
  38. package/dist/runtime/protocol.js +32 -2
  39. package/dist/server.js +112 -15
  40. package/dist/session/event-log.js +131 -10
  41. package/dist/session/fork-matrix.js +12 -0
  42. package/dist/session/transcript-persistence.js +8 -1
  43. package/dist/worktree.js +9 -1
  44. package/package.json +16 -15
package/README.md CHANGED
@@ -206,6 +206,10 @@ answer questions, approve supported tool calls, or stop the agent.
206
206
 
207
207
  - Send screenshots, images, logs, and other files from your phone.
208
208
  - Download reports and artifacts the agent creates.
209
+ - Publish [session apps](docs/apps.md) with web and CLI/TUI views using
210
+ `bivy app publish bivy.app.json`. Linked machines automatically discover web
211
+ preview delivery through Bivy's relay—no per-machine domains, certificates or
212
+ public ports. Terminal views use the existing connection.
209
213
  - Use voice input and read-aloud where supported; provider-backed voice may
210
214
  send audio or text to the selected provider.
211
215
  - Keep a native terminal workflow or use structured chat, depending on the agent.
package/bin/acp-shim.mjs CHANGED
@@ -313,6 +313,13 @@ function onSessionUpdate(params) {
313
313
  if (!content) return "";
314
314
  if (typeof content === "string") return content;
315
315
  if (Array.isArray(content)) return content.map(textOf).join("");
316
+ // ACP's ToolCallContent wraps a ContentBlock: `{ type: "content", content:
317
+ // { type: "text", text } }`. Unwrap it (recursively) so a tool's real output
318
+ // — a command's stdout, a fetched document, a read's text — isn't dropped,
319
+ // which collapsed the tool_result to the bare status ("completed"). opencode
320
+ // and most spec-compliant ACP agents deliver tool output in this wrapped
321
+ // shape, so reading only the bare `{type:"text"}` form lost it in production.
322
+ if (content.type === "content" && content.content != null) return textOf(content.content);
316
323
  if (content.type === "text" && typeof content.text === "string") return content.text;
317
324
  if (typeof content.text === "string") return content.text;
318
325
  return "";
@@ -22,7 +22,7 @@
22
22
  "hidden": false,
23
23
  "supportTier": "supported",
24
24
  "certification": "release-tested",
25
- "testedVersion": "1.18.30",
25
+ "testedVersion": "1.18.32",
26
26
  "headlessFlags": [
27
27
  "run",
28
28
  "-s"
@@ -191,7 +191,7 @@
191
191
  "hidden": false,
192
192
  "supportTier": "supported",
193
193
  "certification": "release-tested",
194
- "testedVersion": "1.0.0",
194
+ "testedVersion": "1.0.41",
195
195
  "headlessFlags": [
196
196
  "--always-approve",
197
197
  "-p",
package/bin/bivy.mjs CHANGED
@@ -2418,6 +2418,57 @@ async function cmdAttach(args = []) {
2418
2418
  console.log(c.green(`Attached ${body.name} (${body.kind}, ${body.size} bytes) to the chat.`));
2419
2419
  }
2420
2420
 
2421
+ // Universal app publishing: every agent that can write JSON and run a command
2422
+ // uses the same contract. Relative static directories are manifest-relative.
2423
+ async function cmdApp(args = []) {
2424
+ const [action, ...rest] = args;
2425
+ if (!action || rest.some((arg) => arg === "--help" || arg === "-h") || ["help", "--help", "-h"].includes(action)) {
2426
+ console.log(`Usage: bivy app publish <manifest.json> [--session <id>]
2427
+ bivy app list [--session <id>]
2428
+ bivy app remove <app-id> [--session <id>]
2429
+
2430
+ Manifest: {"version":1,"name":"My app","views":[
2431
+ {"kind":"web","name":"Website","source":{"kind":"service","port":3000}},
2432
+ {"kind":"terminal","name":"Console","command":"bin/rails","args":["console"]}
2433
+ ]}
2434
+
2435
+ Static web source: {"kind":"static","directory":"./dist"}
2436
+ Publishing does not start commands. Open Apps in the session menu to view or run.
2437
+ Terminal commands run with the node user's permissions, not in a new sandbox.
2438
+ Web previews require operator setup; see docs/apps.md.`);
2439
+ return;
2440
+ }
2441
+ if (!["publish", "list", "remove"].includes(action)) throw new Error("Unknown app command. Run bivy app --help.");
2442
+ const sessionIndex = rest.indexOf("--session");
2443
+ if (sessionIndex >= 0 && (!rest[sessionIndex + 1] || rest[sessionIndex + 1].startsWith("--"))) throw new Error("--session requires an id.");
2444
+ const sessionId = resolveAttachSessionId({ sessionFlag: sessionIndex >= 0 ? rest[sessionIndex + 1] : undefined, env: process.env });
2445
+ if (!sessionId) throw new Error("Set --session <id> or run inside a Bivy agent session.");
2446
+ const positional = rest.filter((_, i) => i !== sessionIndex && (sessionIndex < 0 || i !== sessionIndex + 1));
2447
+ if (positional.some((a) => a.startsWith("-")) || positional.length !== (action === "list" ? 0 : 1)) throw new Error("Invalid arguments. Run bivy app --help.");
2448
+ const body = { sessionId };
2449
+ if (action === "publish") {
2450
+ const file = path.resolve(positional[0]);
2451
+ if (fs.statSync(file).size > 64 * 1024) throw new Error("App manifest exceeds 64 KiB.");
2452
+ const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
2453
+ if (!manifest || typeof manifest !== "object") throw new Error("App manifest must be a JSON object.");
2454
+ if (Array.isArray(manifest.views)) for (const view of manifest.views) {
2455
+ if (view?.kind === "web" && view.source?.kind === "static" && typeof view.source.directory === "string") {
2456
+ view.source.directory = path.resolve(path.dirname(file), view.source.directory);
2457
+ }
2458
+ }
2459
+ body.manifest = manifest;
2460
+ } else if (action === "remove") body.appId = positional[0];
2461
+ const config = loadConfig();
2462
+ if (!(await ensureNodeRunning(config))) throw new Error("Could not reach the Bivy node.");
2463
+ const token = await localDeviceToken(config);
2464
+ const response = await fetch(`${url(config)}/api/apps/${action}`, {
2465
+ method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify(body),
2466
+ });
2467
+ const result = await response.json();
2468
+ if (!response.ok || result.error) throw new Error(result.error || `App command failed (${response.status}).`);
2469
+ console.log(JSON.stringify(result, null, 2));
2470
+ }
2471
+
2421
2472
  // Map a saved session's runtime id to the `bivy run` agent whose native CLI can
2422
2473
  // resume it in a terminal. Only agents with a real native resume qualify; other
2423
2474
  // runtimes (generic-cli, SDK-only) have no terminal resume and open in the web app.
@@ -5045,6 +5096,7 @@ ${c.bold("bivy")} — Bivy node CLI
5045
5096
  ${c.cyan("bivy token")} Print a device token for this node (for 'bivy nodes add' elsewhere)
5046
5097
  ${c.cyan("bivy sessions")} List recent sessions (live + saved) and resume one (alias: ls)
5047
5098
  ${c.cyan("bivy resume")} [n|id] Resume a session directly (default: most recent)
5099
+ ${c.cyan("bivy app")} Publish and list session apps (web and terminal views)
5048
5100
  ${c.cyan("bivy send <id>")} "..." Send a prompt to an existing session and stream the reply
5049
5101
  ${c.cyan("bivy kill <id>")} Stop a session/terminal (--delete also removes a saved session)
5050
5102
  ${c.cyan("bivy prune")} Delete old sessions/workspaces/worktrees (--keep N, --older-than 7d, --dry-run)
@@ -5167,6 +5219,9 @@ An agent's own --help passes through, e.g. 'bivy run claude --help'.`);
5167
5219
  case "send":
5168
5220
  await cmdSend(args);
5169
5221
  break;
5222
+ case "app":
5223
+ await cmdApp(args);
5224
+ break;
5170
5225
  case "attach":
5171
5226
  await cmdAttach(args);
5172
5227
  break;
@@ -415,6 +415,16 @@ function onNotification(m) {
415
415
  bivy({ type: "session.status", status: "working" });
416
416
  return;
417
417
  case "turn/completed": {
418
+ // The completion event is authoritative about a terminal failure: a turn
419
+ // that hit a usage limit (or any provider error) arrives here as
420
+ // `status: "failed"` carrying `turn.error`. Capture it directly instead of
421
+ // depending on a separate `error` notification landing first — otherwise a
422
+ // completion that omits that notification seals as session.done and the
423
+ // user sees a silent empty turn (fail loud, never swallow the error).
424
+ const turnError =
425
+ params?.turn?.error?.message ||
426
+ (params?.turn?.status === "failed" ? params?.error?.message || params?.message || "Codex turn failed" : undefined);
427
+ if (turnError && !sawError) sawError = turnError;
418
428
  // Codex 0.147 can publish turn/completed just before the final
419
429
  // collabAgentToolCall item/completed notification. Sealing immediately
420
430
  // leaves the sub-agent card running forever and drops its persisted
@@ -37,8 +37,8 @@ const piPackage = findDependency(path.join("@earendil-works", "pi-coding-agent")
37
37
  if (!piPackage) process.exit(0);
38
38
 
39
39
  const patches = [
40
- { name: "brace-expansion", version: "5.0.9" },
41
- { name: "undici", version: "8.10.0" },
40
+ { name: "brace-expansion", version: "5.0.12" },
41
+ { name: "undici", version: "8.10.2" },
42
42
  ];
43
43
 
44
44
  for (const patch of patches) {
@@ -4,13 +4,21 @@ import { defineAgentIntegration } from "../definition.js";
4
4
  import { withExactCapabilitySurface } from "../../runtime/types.js";
5
5
  import { createCredentialStore } from "../../runtime/credentials.js";
6
6
  import { ClaudeCodeRuntime, claudeRuntimeFromEnv, claudeSdkInstalled, } from "./runtime.js";
7
- export const CLAUDE_TESTED_VERSION = "0.3.270";
7
+ export const CLAUDE_TESTED_VERSION = "0.3.281";
8
8
  const CLAUDE_CAPABILITIES = withExactCapabilitySurface({
9
9
  toolInterception: true,
10
10
  modelSelection: true,
11
11
  packages: false,
12
12
  resume: true,
13
13
  fork: true,
14
+ // Must mirror ClaudeCodeRuntime.capabilities (runtime.ts): the catalog is a
15
+ // consumed source of truth (the fork-fidelity matrix and any fork-target
16
+ // fidelity preview read these), so under-reporting here makes Claude look
17
+ // like a seeded-only fork target when its runtime actually delivers a
18
+ // byte-exact same-runtime fork (forkTransport) and a full cross-runtime
19
+ // transcript replay INTO it (forkHistoryImport, via importHistoryForFork).
20
+ forkTransport: true,
21
+ forkHistoryImport: true,
14
22
  usageReporting: true,
15
23
  nativeSessionDiscovery: true,
16
24
  nativeSessionAdoption: true,
@@ -9,7 +9,7 @@ import { codexCredentialPreflight } from "../../runtime/codex-preflight.js";
9
9
  import { deleteCodexSession, discoverNativeCodexSessions, loadCodexTranscript, writeCodexRollout, exportCodexRollout, importCodexRollout, } from "../../runtime/codex-sessions.js";
10
10
  import { ProtocolRuntime } from "../../runtime/protocol.js";
11
11
  import { codexSlashCommands } from "../../runtime/slash-commands.js";
12
- export const CODEX_TESTED_VERSION = "0.154.0";
12
+ export const CODEX_TESTED_VERSION = "0.156.1";
13
13
  const CODEX_AVAILABLE_CACHE = new Map();
14
14
  function codexCommand() {
15
15
  return process.env.BIVY_CODEX_BIN?.trim() || "codex";
@@ -146,6 +146,14 @@ export function codexIntegration(origin) {
146
146
  resume: true,
147
147
  packages: false,
148
148
  fork: true,
149
+ // Mirror the ProtocolRuntime this integration builds (createCodexRuntime
150
+ // above wires exportForFork+importForFork and writeHistory, which flip
151
+ // capabilities.forkTransport/forkHistoryImport on). The catalog is a
152
+ // consumed source of truth (fork-fidelity matrix / fork-target preview),
153
+ // so omitting these made Codex look seeded-only when its runtime does a
154
+ // byte-exact codex→codex fork and a full transcript replay INTO it.
155
+ forkTransport: true,
156
+ forkHistoryImport: true,
149
157
  usageReporting: true,
150
158
  interactiveTui: installed,
151
159
  nativeSessionDiscovery: true,
@@ -5,7 +5,7 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { defineAgentIntegration } from "../definition.js";
7
7
  import { PI_CAPABILITIES } from "./capabilities.js";
8
- export const PI_TESTED_VERSION = "0.85.1";
8
+ export const PI_TESTED_VERSION = "0.87.1";
9
9
  export function piAgentDir() {
10
10
  return process.env.PI_CODING_AGENT_DIR?.trim() || path.join(os.homedir(), ".pi", "agent");
11
11
  }
@@ -268,8 +268,29 @@ class PiSession {
268
268
  * reload. Async because that resolution reads the store.
269
269
  */
270
270
  async getModels() {
271
- const models = await this.session.modelRuntime.getAvailable();
272
- return models.map((model) => toModelInfo(model, true));
271
+ const runtime = this.session.modelRuntime;
272
+ try {
273
+ const models = await runtime.getAvailable();
274
+ return models.map((model) => toModelInfo(model, true));
275
+ }
276
+ catch (error) {
277
+ // The runtime's availability refresh is a Promise.all across every
278
+ // provider's auth check, so one broken provider (an unreachable custom
279
+ // endpoint, a single failing credential read) rejects the whole catalog
280
+ // and the picker shows nothing. Degrade to per-provider listing and
281
+ // skip only the broken ones; rethrow only when truly nothing survives.
282
+ const models = [];
283
+ for (const provider of runtime.getProviders()) {
284
+ try {
285
+ for (const model of await runtime.getAvailable(provider.id))
286
+ models.push(toModelInfo(model, true));
287
+ }
288
+ catch { /* this provider is broken — the rest still list */ }
289
+ }
290
+ if (models.length)
291
+ return models;
292
+ throw error instanceof Error ? error : new Error(String(error));
293
+ }
273
294
  }
274
295
  /**
275
296
  * Every model Pi knows about — built-in and custom, regardless of whether
@@ -281,7 +302,10 @@ class PiSession {
281
302
  async getAllModels() {
282
303
  const runtime = this.session.modelRuntime;
283
304
  // Refresh availability first so `hasConfiguredAuth` reflects current creds.
284
- await runtime.getAvailable();
305
+ // Best-effort: a failed refresh (one broken provider rejects the shared
306
+ // Promise.all) must not blank the full catalog — the flags then reflect
307
+ // the last successful refresh, which the picker can still act on.
308
+ await runtime.getAvailable().catch(() => { });
285
309
  return runtime.getModels().map((model) => toModelInfo(model, runtime.hasConfiguredAuth(model.provider)));
286
310
  }
287
311
  /** Reload the projected models.json in-place so a live session immediately
@@ -43,7 +43,7 @@ export const AGENT_PROFILES = {
43
43
  // Approve/Deny + session/load resume + a real model picker), the same bar Pi,
44
44
  // Claude Code, and Codex clear. See `acp` below for the version fallback.
45
45
  supportTier: "supported",
46
- testedVersion: "1.18.30",
46
+ testedVersion: "1.18.32",
47
47
  blurb: "The most widely used open-source coding harness (OpenCode CLI).",
48
48
  // `opencode run -s <id> "<prompt>"` continues a prior session by its own id
49
49
  // (`-s, --session session id to continue`, per `opencode run --help`).
@@ -356,8 +356,11 @@ export const AGENT_PROFILES = {
356
356
  behaviors: { preflight: "grok", prepare: "grok-auth", nativeSessions: "grok" },
357
357
  command: "grok",
358
358
  packageName: "grok (curl -fsSL https://x.ai/cli/install.sh | bash)",
359
+ // Supported tier: Grok runs on the governed ACP path by default (per-tool
360
+ // Approve/Deny + native resume + live model state), the same bar Pi, Claude
361
+ // Code, Codex, and OpenCode clear. See `acp` below for the version fallback.
359
362
  supportTier: "supported",
360
- testedVersion: "1.0.0",
363
+ testedVersion: "1.0.41",
361
364
  authOwner: "mixed",
362
365
  blurb: "xAI's official Grok coding agent (Grok CLI) — SuperGrok/X subscription or API key.",
363
366
  // Official CLI: `grok -p "<prompt>"` (alias `--single`) runs one headless
@@ -398,14 +401,32 @@ export const AGENT_PROFILES = {
398
401
  flag: "-m",
399
402
  models: [
400
403
  // Official Grok CLI (1.x) advertises grok-4.6 as the default subscription
401
- // model (verified against `grok models`); the older grok-4.5 / grok-4-latest
402
- // / grok-code-fast-1 ids now return "unknown model id". Keep the list
403
- // honest; operators can override with BIVY_GROK_MODELS if their install
404
- // exposes more.
404
+ // model (verified against the authenticated `~/.grok/models_cache.json`,
405
+ // origin cli-chat-proxy.grok.com/v1/models); the older grok-4.5 /
406
+ // grok-4-latest / grok-code-fast-1 ids now return "unknown model id".
407
+ // Keep the list honest; operators can override with BIVY_GROK_MODELS if
408
+ // their install exposes more.
405
409
  { id: "grok-4.6", name: "Grok 4.6", provider: "xai" },
406
410
  ],
407
411
  },
412
+ // Grok's reasoning models expose a `--reasoning-effort <EFFORT>` flag
413
+ // (verified against `grok --help`, 1.0.0); the authenticated model catalog
414
+ // lists low/medium/high/xhigh with `high` as the default. `{level}` fills
415
+ // the value; insertAt: 0 keeps the trailing `-p` prompt flag last so the
416
+ // prompt still lands as its value. Threads through the resume path too, so a
417
+ // continued turn keeps the chosen effort. Override with BIVY_GROK_THINKING.
418
+ thinking: { levels: ["low", "medium", "high", "xhigh"], default: "high", template: ["--reasoning-effort", "{level}"], insertAt: 0 },
408
419
  promptMode: "argv",
420
+ // `grok agent stdio` ("Run the agent over stdio") is a full ACP server
421
+ // (protocolVersion 1: session/new, session/prompt, session/request_permission,
422
+ // loadSession resume, live model state) — the governed ProtocolRuntime path
423
+ // that supersedes the `--always-approve` pipe above with real per-tool
424
+ // Approve/Deny. Validated against grok 1.0.41, so it is ON by default
425
+ // (`preferred`) — gated on the binary actually describing the `agent`
426
+ // subcommand ("Run Grok without the interactive UI" in `grok --help`), so an
427
+ // older CLI falls back to the pipe path rather than opening a dead session.
428
+ // Force the pipe path back with BIVY_GROK_ACP=0.
429
+ acp: { args: ["agent", "stdio"], helpToken: "without the interactive ui", preferred: true },
409
430
  install: {
410
431
  kind: "curl",
411
432
  display: "curl -fsSL https://x.ai/cli/install.sh | bash",