@bivy/bivy 0.0.0 → 0.1.0-staging.10

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 (147) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +265 -5
  3. package/bin/acp-shim.mjs +298 -0
  4. package/bin/agent-manifest.json +277 -0
  5. package/bin/bivy.mjs +4215 -0
  6. package/bin/codex-app-server-shim.mjs +447 -0
  7. package/bin/patch-pi-dependencies.mjs +44 -0
  8. package/bin/port-picker.mjs +40 -0
  9. package/bin/prune-sessions.mjs +52 -0
  10. package/bin/sessions-list.mjs +27 -0
  11. package/bin/shim-path.mjs +126 -0
  12. package/bin/uninstall-paths.mjs +48 -0
  13. package/dist/approval.js +87 -0
  14. package/dist/attach.js +248 -0
  15. package/dist/auth.js +258 -0
  16. package/dist/bivy-login.js +180 -0
  17. package/dist/browser-open.js +50 -0
  18. package/dist/control-plane-tasks.js +236 -0
  19. package/dist/data-dir.js +25 -0
  20. package/dist/device-registry.js +201 -0
  21. package/dist/e2e.js +70 -0
  22. package/dist/ephemeral-exec.js +109 -0
  23. package/dist/exec.js +209 -0
  24. package/dist/git-auth.js +155 -0
  25. package/dist/github-app-auth.js +107 -0
  26. package/dist/github-app-connect.js +235 -0
  27. package/dist/github-app-manifest.js +82 -0
  28. package/dist/github-app-sync-cli.js +93 -0
  29. package/dist/github-app-vault.js +106 -0
  30. package/dist/github-apps.js +121 -0
  31. package/dist/github-connect-repo.js +74 -0
  32. package/dist/github-device-auth.js +109 -0
  33. package/dist/github-tasks.js +650 -0
  34. package/dist/guard.js +109 -0
  35. package/dist/harness/cache-evict.js +88 -0
  36. package/dist/harness/checkpoint.js +0 -0
  37. package/dist/harness/cow-clone.js +84 -0
  38. package/dist/harness/dep-cache.js +78 -0
  39. package/dist/harness/disk-admission.js +46 -0
  40. package/dist/harness/egress.js +30 -0
  41. package/dist/harness/manager.js +97 -0
  42. package/dist/harness/mcp-config-formats.js +164 -0
  43. package/dist/harness/mcp-config.js +111 -0
  44. package/dist/harness/mcp-inject.js +134 -0
  45. package/dist/harness/mcp-proxy-cli.js +88 -0
  46. package/dist/harness/mcp-proxy.js +150 -0
  47. package/dist/harness/net-proxy.js +120 -0
  48. package/dist/harness/sandbox.js +96 -0
  49. package/dist/history-sync.js +26 -0
  50. package/dist/hosted-endpoints.d.mts +14 -0
  51. package/dist/hosted-endpoints.mjs +35 -0
  52. package/dist/identity.js +153 -0
  53. package/dist/integrations/index.js +4 -0
  54. package/dist/integrations/manager.js +279 -0
  55. package/dist/integrations/oauth.js +78 -0
  56. package/dist/integrations/registry.js +239 -0
  57. package/dist/integrations/store.js +54 -0
  58. package/dist/integrations/types.js +1 -0
  59. package/dist/linear-tasks.js +49 -0
  60. package/dist/metadata.js +226 -0
  61. package/dist/multiplexer.js +79 -0
  62. package/dist/native-pi.js +38 -0
  63. package/dist/node-stats.js +237 -0
  64. package/dist/pairing-crypto.js +105 -0
  65. package/dist/policy/conditions.js +103 -0
  66. package/dist/policy/policy-engine.js +20 -0
  67. package/dist/policy/risk.js +18 -0
  68. package/dist/policy/ruleset.js +113 -0
  69. package/dist/policy/run-policy.js +108 -0
  70. package/dist/policy/session-reroute.js +96 -0
  71. package/dist/pty-runner.py +95 -0
  72. package/dist/question.js +146 -0
  73. package/dist/redact.js +97 -0
  74. package/dist/relay-attach.js +345 -0
  75. package/dist/relay-chunk.js +73 -0
  76. package/dist/relay-cli-crypto.js +70 -0
  77. package/dist/relay-client.js +372 -0
  78. package/dist/relay-setup.js +262 -0
  79. package/dist/repo-workspace.js +208 -0
  80. package/dist/runtime/adoption.js +45 -0
  81. package/dist/runtime/agent-service-bin.js +149 -0
  82. package/dist/runtime/agent-service.js +439 -0
  83. package/dist/runtime/ansi.js +27 -0
  84. package/dist/runtime/anthropic-preflight.js +80 -0
  85. package/dist/runtime/claude-code.js +1408 -0
  86. package/dist/runtime/cli-parsers.js +647 -0
  87. package/dist/runtime/codex-auth.js +168 -0
  88. package/dist/runtime/codex-preflight.js +60 -0
  89. package/dist/runtime/codex-sessions.js +279 -0
  90. package/dist/runtime/control-plane-location.js +74 -0
  91. package/dist/runtime/credential-ingest.js +122 -0
  92. package/dist/runtime/credential-provisioning.js +79 -0
  93. package/dist/runtime/credential-store.js +435 -0
  94. package/dist/runtime/credentials.js +153 -0
  95. package/dist/runtime/host.js +153 -0
  96. package/dist/runtime/index.js +1552 -0
  97. package/dist/runtime/local-model-store.js +194 -0
  98. package/dist/runtime/location-registry.js +28 -0
  99. package/dist/runtime/model-catalog.js +97 -0
  100. package/dist/runtime/model-namer.js +85 -0
  101. package/dist/runtime/native-process-scan.js +102 -0
  102. package/dist/runtime/native-session-discovery.js +103 -0
  103. package/dist/runtime/normalize.js +75 -0
  104. package/dist/runtime/oauth/model-oauth-providers.js +75 -0
  105. package/dist/runtime/oauth/model-oauth.js +324 -0
  106. package/dist/runtime/opencode-preflight.js +55 -0
  107. package/dist/runtime/pi-auth.js +82 -0
  108. package/dist/runtime/pi-oauth.js +52 -0
  109. package/dist/runtime/pi-session-discovery.js +42 -0
  110. package/dist/runtime/pi.js +539 -0
  111. package/dist/runtime/process.js +499 -0
  112. package/dist/runtime/protocol.js +646 -0
  113. package/dist/runtime/remote.js +541 -0
  114. package/dist/runtime/rpc-protocol.js +56 -0
  115. package/dist/runtime/ruleset-store.js +117 -0
  116. package/dist/runtime/session-location.js +50 -0
  117. package/dist/runtime/types.js +17 -0
  118. package/dist/secrets-cli.js +134 -0
  119. package/dist/secrets.js +264 -0
  120. package/dist/server.js +9435 -0
  121. package/dist/session/bivy-session.js +1 -0
  122. package/dist/session/checkpoint-pack.js +133 -0
  123. package/dist/session/event-log.js +340 -0
  124. package/dist/session/fork-dirty.js +73 -0
  125. package/dist/session/fork-prereqs.js +61 -0
  126. package/dist/session/fork.js +90 -0
  127. package/dist/session/native-import.js +56 -0
  128. package/dist/session/reconnect.js +168 -0
  129. package/dist/session/replication-service.js +236 -0
  130. package/dist/session/replication.js +106 -0
  131. package/dist/session/replicator.js +140 -0
  132. package/dist/session/session-new-dedupe.js +42 -0
  133. package/dist/session/sibling-client.js +201 -0
  134. package/dist/session/transcript-merge.js +131 -0
  135. package/dist/session/transcript-normal.js +195 -0
  136. package/dist/session/workspace-context.js +1 -0
  137. package/dist/session-event-coalescer.js +50 -0
  138. package/dist/session-identity.js +34 -0
  139. package/dist/session-ref.js +65 -0
  140. package/dist/stt-cli.js +131 -0
  141. package/dist/stt.js +168 -0
  142. package/dist/terminal.js +409 -0
  143. package/dist/wire-format.js +67 -0
  144. package/dist/worktree-provision.js +118 -0
  145. package/dist/worktree.js +117 -0
  146. package/package.json +40 -6
  147. package/public/qr.js +464 -0
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ // Shared credential resolver for agent env injection.
4
+ //
5
+ // A node-level resolver so every agent runtime can reuse the model-provider
6
+ // logins the user did once, instead of re-authenticating per agent. It reads
7
+ // Bivy's own credential store (credential-store.ts) DIRECTLY — api keys and
8
+ // non-expired OAuth tokens need no Pi at all. Only an expired OAuth token
9
+ // touches Pi, via the isolated pi-oauth bridge, to run the provider's refresh.
10
+ //
11
+ // This keeps Bivy's hot credential path decoupled from Pi: Pi is just another
12
+ // agent that reads the same store.
13
+ import { createCredentialVault } from "./credential-store.js";
14
+ import { refreshModelOAuth } from "./oauth/model-oauth.js";
15
+ /** Refresh an OAuth token this many ms before it expires (clock-skew guard). */
16
+ const OAUTH_REFRESH_SKEW_MS = 60_000;
17
+ /** Resolver over Bivy's credential store, with OAuth refresh-on-read via the bridge. */
18
+ export class NodeCredentialResolver {
19
+ credsDir;
20
+ store;
21
+ constructor(credsDir) {
22
+ this.credsDir = credsDir;
23
+ this.store = createCredentialVault(credsDir);
24
+ }
25
+ async getCredential(provider) {
26
+ const id = provider.trim().toLowerCase();
27
+ if (!id)
28
+ return undefined;
29
+ const cred = await this.store.read(id).catch(() => undefined);
30
+ if (!cred)
31
+ return undefined;
32
+ if (cred.type === "api_key") {
33
+ const token = typeof cred.key === "string" ? cred.key : "";
34
+ if (!token)
35
+ return undefined;
36
+ return { provider: id, kind: "api_key", token, ...(cred.env ? { env: cred.env } : {}) };
37
+ }
38
+ // OAuth: use the stored access token while it's fresh; otherwise ask Pi to
39
+ // run the provider's refresh (which rotates + persists back into our store
40
+ // under the store lock).
41
+ let token = typeof cred.access === "string" ? cred.access : "";
42
+ const expires = Number(cred.expires) || 0;
43
+ if (!token || expires <= Date.now() + OAUTH_REFRESH_SKEW_MS) {
44
+ const refreshed = await refreshModelOAuth(this.credsDir, id).catch(() => undefined);
45
+ if (refreshed)
46
+ token = refreshed;
47
+ }
48
+ if (!token)
49
+ return undefined;
50
+ const env = cred.env;
51
+ return { provider: id, kind: "oauth", token, ...(env ? { env } : {}) };
52
+ }
53
+ /** Providers with a stored credential — the vault's contents (not ambient env). */
54
+ async listConfigured() {
55
+ const infos = await this.store.list().catch(() => []);
56
+ return infos.map((info) => info.providerId);
57
+ }
58
+ }
59
+ /** Build the shared credential resolver from the node's credential vault dir. */
60
+ export function createCredentialStore(credsDir) {
61
+ return new NodeCredentialResolver(credsDir);
62
+ }
63
+ // Conventional environment variables agent CLIs/SDKs read for each provider. This
64
+ // is how a login the user did once inside Bivy reaches an arbitrary agent process:
65
+ // the agent doesn't know about Bivy's vault, but it does read these env vars.
66
+ const PROVIDER_ENV_KEYS = {
67
+ anthropic: "ANTHROPIC_API_KEY",
68
+ openai: "OPENAI_API_KEY",
69
+ openrouter: "OPENROUTER_API_KEY",
70
+ google: "GEMINI_API_KEY",
71
+ gemini: "GEMINI_API_KEY",
72
+ "google-vertex": "GOOGLE_API_KEY",
73
+ groq: "GROQ_API_KEY",
74
+ mistral: "MISTRAL_API_KEY",
75
+ deepseek: "DEEPSEEK_API_KEY",
76
+ xai: "XAI_API_KEY",
77
+ together: "TOGETHER_API_KEY",
78
+ fireworks: "FIREWORKS_API_KEY",
79
+ cohere: "COHERE_API_KEY",
80
+ perplexity: "PERPLEXITY_API_KEY",
81
+ };
82
+ /** Env var an api-key credential for `provider` should be exposed under. */
83
+ export function apiKeyEnvVar(provider) {
84
+ return PROVIDER_ENV_KEYS[provider] ?? `${provider.replace(/[^a-z0-9]+/gi, "_").toUpperCase()}_API_KEY`;
85
+ }
86
+ // A custom endpoint's base URL is stored in its credential `env` under one of
87
+ // these vars. When that endpoint is the session's ACTIVE provider we also alias
88
+ // its key to the matching standard key var, so an agent that reads OPENAI_* /
89
+ // AZURE_OPENAI_* / ANTHROPIC_* picks up the custom endpoint. Injected only for
90
+ // the active provider — a global base-URL var would hijack the real provider.
91
+ const BASE_URL_TO_KEY_VAR = {
92
+ OPENAI_BASE_URL: "OPENAI_API_KEY",
93
+ AZURE_OPENAI_BASE_URL: "AZURE_OPENAI_API_KEY",
94
+ ANTHROPIC_BASE_URL: "ANTHROPIC_API_KEY",
95
+ };
96
+ /**
97
+ * Map every credential the node holds (or a chosen subset) to the environment
98
+ * variables an agent process reads, so one sign-in inside Bivy serves whatever
99
+ * runtime/model the user selects — not just the Pi agent. Anthropic OAuth
100
+ * (Claude Pro/Max) is exposed as CLAUDE_CODE_OAUTH_TOKEN; every other credential
101
+ * is exposed as the provider's API-key var.
102
+ *
103
+ * Provider-scoped `env` (custom base URLs) is injected ONLY for `activeProvider`
104
+ * — the session's selected provider. Base-URL vars like OPENAI_BASE_URL are
105
+ * global to the agent process, so injecting a custom endpoint's base URL for a
106
+ * non-active provider would silently redirect the real provider. Best-effort: a
107
+ * provider that fails to resolve is skipped rather than failing the session.
108
+ */
109
+ export async function buildAgentCredentialEnv(store, providers, activeProvider) {
110
+ const ids = providers ?? (store.listConfigured ? await store.listConfigured().catch(() => []) : []);
111
+ const active = activeProvider?.trim().toLowerCase() || undefined;
112
+ // Process the active provider LAST so its base URL and standard-key alias win
113
+ // over a same-named var from another provider (e.g. a real "openai" key var
114
+ // must not clobber an active custom endpoint's aliased OPENAI_API_KEY).
115
+ const ordered = active ? [...ids].sort((a, b) => Number(a === active) - Number(b === active)) : ids;
116
+ const env = {};
117
+ for (const id of ordered) {
118
+ let cred;
119
+ try {
120
+ cred = await store.getCredential(id);
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ if (!cred)
126
+ continue;
127
+ const isActive = !!active && cred.provider === active;
128
+ if (cred.kind === "oauth") {
129
+ // OAuth *subscription* tokens are provider-specific and are not accepted
130
+ // as a plain API key. Only Anthropic has a documented env var an external
131
+ // agent reads (Claude Code's OAuth token); other providers' subscription
132
+ // logins can't be handed off this way, so don't emit a misleading key.
133
+ if (cred.provider !== "anthropic")
134
+ continue;
135
+ Object.assign(env, cred.env ?? {});
136
+ env.CLAUDE_CODE_OAUTH_TOKEN = cred.token;
137
+ }
138
+ else {
139
+ env[apiKeyEnvVar(cred.provider)] = cred.token;
140
+ // A custom endpoint's base URL (and standard-key alias) is injected only
141
+ // when it's the active provider, so it can't hijack the real provider.
142
+ if (isActive && cred.env) {
143
+ Object.assign(env, cred.env);
144
+ for (const baseVar of Object.keys(cred.env)) {
145
+ const keyVar = BASE_URL_TO_KEY_VAR[baseVar];
146
+ if (keyVar && cred.token)
147
+ env[keyVar] = cred.token;
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return env;
153
+ }
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import { listRuntimes, makeRuntime } from "./index.js";
4
+ import { RemoteRuntime, connectSocketTransport } from "./remote.js";
5
+ function remoteRuntimeSelection() {
6
+ const flag = process.env.BIVY_REMOTE_RUNTIME?.trim();
7
+ if (!flag || flag === "0" || flag.toLowerCase() === "false")
8
+ return null;
9
+ const addr = process.env.BIVY_REMOTE_RUNTIME_ADDR?.trim();
10
+ if (!addr)
11
+ return null; // enabled but nowhere to connect — stay in-process
12
+ const all = ["1", "true", "all", "*"].includes(flag.toLowerCase());
13
+ const ids = all ? null : new Set(flag.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean));
14
+ return { addr, enabled: (id) => all || Boolean(ids?.has(id)) };
15
+ }
16
+ /**
17
+ * Whether the remote-runtime path is active at all (flag set AND an address to
18
+ * connect to). Stage 3 gates startup adoption and the control-plane location
19
+ * registry on this so those hooks are inert — and the daemon byte-identical —
20
+ * when the flag is off (docs/agent-node-decoupling.md).
21
+ */
22
+ export function remoteRuntimeEnabled() {
23
+ return remoteRuntimeSelection() !== null;
24
+ }
25
+ /** Fill a RuntimeInfo's partial capabilities into a full, default-false surface. */
26
+ function fullCapabilities(partial) {
27
+ return { toolInterception: false, modelSelection: false, packages: false, resume: false, fork: false, ...partial };
28
+ }
29
+ export function enforcementLevelFor(capabilities, runtimeId) {
30
+ if (capabilities.toolInterception)
31
+ return "strong";
32
+ if (runtimeId === "generic-cli")
33
+ return "boundary";
34
+ return "observe_only";
35
+ }
36
+ /**
37
+ * Small runtime host seam above concrete adapters. The node should talk to this
38
+ * for registry lookup, adapter caching, session create/open/list, and capability
39
+ * reporting instead of scattering runtime glue through server.ts.
40
+ */
41
+ export class RuntimeHost {
42
+ options;
43
+ cache = new Map();
44
+ constructor(options) {
45
+ this.options = options;
46
+ }
47
+ list(currentId) {
48
+ return listRuntimes(currentId);
49
+ }
50
+ resolveRuntimeId(requested, fallback) {
51
+ const wantId = (requested || fallback).toLowerCase();
52
+ const info = this.list().find((candidate) => candidate.id === wantId);
53
+ if (!info)
54
+ throw new Error(`Unknown agent: ${wantId}`);
55
+ if (info.status !== "available")
56
+ throw new Error(`${info.displayName} is not available on this node yet.`);
57
+ return wantId;
58
+ }
59
+ get(requested, fallback, sandbox) {
60
+ // Remote path (opt-in, reversible): return a RemoteRuntime that drives the
61
+ // runtime in a separate agent service. Checked BEFORE resolveRuntimeId so a
62
+ // runtime the daemon can't run locally (e.g. the Claude SDK isn't installed
63
+ // here) is still offloadable — remote availability is the service's business.
64
+ const remote = remoteRuntimeSelection();
65
+ const wantId = (requested || fallback).toLowerCase();
66
+ if (remote && remote.enabled(wantId))
67
+ return this.getRemote(wantId, remote.addr, sandbox);
68
+ const id = this.resolveRuntimeId(requested, fallback);
69
+ // A per-session sandbox override bakes into the runtime's launch flags, so it
70
+ // must not share the cached instance built for the node default. Key such
71
+ // runtimes by tier; runtimes without an override keep the plain-id entry.
72
+ const key = sandbox ? `${id}::sandbox=${sandbox}` : id;
73
+ let rt = this.cache.get(key);
74
+ if (!rt) {
75
+ rt = makeRuntime({ ...this.options, runtime: id, sandbox });
76
+ this.cache.set(key, rt);
77
+ if (!sandbox && rt.id !== id)
78
+ this.cache.set(rt.id, rt);
79
+ }
80
+ return rt;
81
+ }
82
+ /**
83
+ * Per-session remote routing (Stage 3, docs/agent-node-decoupling.md): build (or
84
+ * reuse) a RemoteRuntime pointed at a SPECIFIC agent-service address, rather than
85
+ * the process-wide BIVY_REMOTE_RUNTIME_ADDR. An adopted session may live on a
86
+ * different service than the node default, so re-attach must target the address
87
+ * recorded for that session. Caching is keyed by address, so distinct services
88
+ * get distinct runtime facades.
89
+ */
90
+ getRemoteAt(id, addr, sandbox) {
91
+ return this.getRemote(id, addr, sandbox);
92
+ }
93
+ /** Build (and cache) a RemoteRuntime facade for `id`, addressed at `addr`. */
94
+ getRemote(id, addr, sandbox) {
95
+ const key = `remote::${addr}::${id}${sandbox ? `::sandbox=${sandbox}` : ""}`;
96
+ let rt = this.cache.get(key);
97
+ if (!rt) {
98
+ // Passing `id` as currentId includes it even if it's hidden from the picker,
99
+ // so the capability/displayName lookup works for any known runtime.
100
+ const info = this.list(id).find((candidate) => candidate.id === id);
101
+ if (!info)
102
+ throw new Error(`Unknown agent: ${id}`);
103
+ rt = new RemoteRuntime({
104
+ targetRuntime: id,
105
+ displayName: info.displayName,
106
+ capabilities: fullCapabilities(info.capabilities),
107
+ sandbox,
108
+ agentServiceAddress: addr,
109
+ connect: () => connectSocketTransport(addr),
110
+ });
111
+ this.cache.set(key, rt);
112
+ }
113
+ return rt;
114
+ }
115
+ summary(runtime) {
116
+ return {
117
+ id: runtime.id,
118
+ displayName: runtime.displayName,
119
+ capabilities: runtime.capabilities,
120
+ enforcementLevel: enforcementLevelFor(runtime.capabilities, runtime.id),
121
+ };
122
+ }
123
+ async createSession(runtime, options) {
124
+ return runtime.createSession(options);
125
+ }
126
+ async openSession(runtime, options) {
127
+ return runtime.openSession(options);
128
+ }
129
+ async listSessions(runtime) {
130
+ return runtime.listSessions();
131
+ }
132
+ /** Ask a runtime to forget a session from its own store (see
133
+ * AgentRuntime.deleteSession). No-ops to `false` for runtimes that keep no
134
+ * store of their own. */
135
+ async deleteSession(runtime, sessionId, sessionFile) {
136
+ return (await runtime.deleteSession?.(sessionId, sessionFile)) ?? false;
137
+ }
138
+ /** Fast, build-free transcript read for opening a session (see AgentRuntime.readMessages). */
139
+ readMessages(runtime, sessionFile) {
140
+ return runtime.readMessages?.(sessionFile);
141
+ }
142
+ /** Enumerate a runtime's own provider-native sessions this node didn't start
143
+ * (see AgentRuntime.discoverNativeSessions, issue #156). Empty for a runtime
144
+ * that doesn't support discovery, or on any adapter-level failure. */
145
+ async discoverNativeSessions(runtime) {
146
+ try {
147
+ return (await runtime.discoverNativeSessions?.()) ?? [];
148
+ }
149
+ catch {
150
+ return [];
151
+ }
152
+ }
153
+ }