@bivy/bivy 0.7.0 → 0.8.0
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 +128 -14
- package/bin/agent-manifest.json +3 -2
- package/bin/bivy.mjs +36 -4
- package/dist/github-device-auth.js +16 -0
- package/dist/harness/mcp-config.js +89 -6
- package/dist/harness/mcp-inject.js +31 -8
- package/dist/metadata.js +32 -0
- package/dist/policy/conditions.js +54 -3
- package/dist/policy/run-policy.js +2 -1
- package/dist/policy/session-reroute.js +66 -0
- package/dist/repo-workspace.js +19 -0
- package/dist/runtime/index.js +127 -18
- package/dist/runtime/protocol.js +15 -0
- package/dist/secrets.js +6 -2
- package/dist/server.js +372 -23
- package/package.json +1 -1
|
@@ -21,6 +21,17 @@
|
|
|
21
21
|
// whether to suppress the turn's error toast before kicking off the async swap +
|
|
22
22
|
// retry (`applyReroute`). Reroute happens only at the turn boundary, so there is
|
|
23
23
|
// no partial-work hazard.
|
|
24
|
+
//
|
|
25
|
+
// It also plans the OTHER in-place recovery a live session can do: waiting out a
|
|
26
|
+
// provider usage/rate limit and re-sending the same prompt when the window
|
|
27
|
+
// resets (`planResume`). Unlike a reroute (which the controller applies itself),
|
|
28
|
+
// a resume can be hours away and must survive a daemon restart, so scheduling +
|
|
29
|
+
// persistence live in the caller (src/server.ts) — the controller only decides
|
|
30
|
+
// whether a resume is warranted and by when.
|
|
31
|
+
/** Below this, a "retry" is ordinary backoff (seconds) — not worth deferring an
|
|
32
|
+
* interactive turn for; let it surface. A real usage/rate window reset is
|
|
33
|
+
* minutes-to-days out and always clears this bar. */
|
|
34
|
+
const MIN_RESUME_DELAY_MS = 60_000;
|
|
24
35
|
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
25
36
|
export class SessionRerouteController {
|
|
26
37
|
deps;
|
|
@@ -50,6 +61,61 @@ export class SessionRerouteController {
|
|
|
50
61
|
attempt: this.attempt,
|
|
51
62
|
rerouteCount: this.rerouteCount,
|
|
52
63
|
});
|
|
64
|
+
if (decision.action !== "reroute")
|
|
65
|
+
return null;
|
|
66
|
+
return this.rerouteFrom(decision, currentModel);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Decide whether this turn error should be recovered by WAITING for a provider
|
|
70
|
+
* usage/rate limit to reset and re-sending the same prompt. Returns a plan the
|
|
71
|
+
* caller should persist + schedule, or null (surface the error as usual).
|
|
72
|
+
*
|
|
73
|
+
* `resetsAtHint` is the authoritative reset time when the caller has one (the
|
|
74
|
+
* provider's structured usage snapshot) — essential for a multi-day "weekly"
|
|
75
|
+
* window, whose error text only states a time-of-day. `now` is injectable for
|
|
76
|
+
* deterministic tests. Pure w.r.t. the controller's counters.
|
|
77
|
+
*/
|
|
78
|
+
planResume(rawError, currentModel, opts = {}) {
|
|
79
|
+
if (this.applying)
|
|
80
|
+
return null;
|
|
81
|
+
const now = opts.now ?? Date.now();
|
|
82
|
+
const decision = this.deps.policy.decide({
|
|
83
|
+
routing: { model: currentModel },
|
|
84
|
+
error: rawError,
|
|
85
|
+
attempt: this.attempt,
|
|
86
|
+
rerouteCount: this.rerouteCount,
|
|
87
|
+
resetsAtHint: opts.resetsAtHint,
|
|
88
|
+
});
|
|
89
|
+
if (decision.action !== "retry")
|
|
90
|
+
return null;
|
|
91
|
+
// Only defer for a concrete recovery window — a provider reset, or a delay
|
|
92
|
+
// long enough that it's clearly a limit rather than routine backoff.
|
|
93
|
+
if (decision.resetsAt === undefined && decision.delayMs < MIN_RESUME_DELAY_MS)
|
|
94
|
+
return null;
|
|
95
|
+
// Resolve the due time (provider reset when known, else backoff) and floor it
|
|
96
|
+
// to at least MIN_RESUME_DELAY_MS in the FUTURE. A reset time can be in the
|
|
97
|
+
// past or ~now — a stale/elapsed reset, clock skew, or (most often) a window
|
|
98
|
+
// that already lapsed while the daemon was down — and using it verbatim yields
|
|
99
|
+
// a 0ms delay. The caller arms a timer at that delay, so a 0ms resume re-sends
|
|
100
|
+
// instantly, re-hits the still-standing limit, and re-schedules 0ms again: a
|
|
101
|
+
// tight loop that pins a CPU core and never settles. Flooring turns a
|
|
102
|
+
// not-yet-cleared limit into a slow retry the attempt budget can still park.
|
|
103
|
+
const rawDueMs = decision.resetsAt ? Date.parse(decision.resetsAt) : now + decision.delayMs;
|
|
104
|
+
const dueMs = Math.max(Number.isFinite(rawDueMs) ? rawDueMs : now, now + MIN_RESUME_DELAY_MS);
|
|
105
|
+
return {
|
|
106
|
+
condition: decision.condition,
|
|
107
|
+
summary: decision.summary,
|
|
108
|
+
delayMs: dueMs - now,
|
|
109
|
+
resumeAt: new Date(dueMs).toISOString(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Advance the attempt budget once the caller has committed to a resume, so a
|
|
113
|
+
* limit that re-fires after the reset counts toward `maxAttempts` and can
|
|
114
|
+
* eventually exhaust (→ park) instead of looping forever. */
|
|
115
|
+
noteResumeApplied() {
|
|
116
|
+
this.attempt += 1;
|
|
117
|
+
}
|
|
118
|
+
rerouteFrom(decision, currentModel) {
|
|
53
119
|
if (decision.action !== "reroute")
|
|
54
120
|
return null;
|
|
55
121
|
const model = decision.routing.model;
|
package/dist/repo-workspace.js
CHANGED
|
@@ -100,6 +100,25 @@ export async function resolveGitHubToken(env = process.env) {
|
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether the GitHub CLI (`gh`) is installed on this machine — used only to
|
|
105
|
+
* shade the "no GitHub token" message: when `gh` is present but `gh auth token`
|
|
106
|
+
* gave us nothing, the user is one `gh auth login` away, so the picker can say
|
|
107
|
+
* so. It never means `gh` is REQUIRED — `bivy github:connect` is the primary
|
|
108
|
+
* path and needs no CLI (see resolveGitHubToken). Mirrors the `command -v`
|
|
109
|
+
* probe in secrets.ts.
|
|
110
|
+
*/
|
|
111
|
+
export async function ghCliInstalled() {
|
|
112
|
+
const which = process.platform === "win32" ? "where" : "command";
|
|
113
|
+
const args = process.platform === "win32" ? ["gh"] : ["-v", "gh"];
|
|
114
|
+
try {
|
|
115
|
+
await exec(which, args, process.platform === "win32" ? {} : { shell: true });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
103
122
|
/**
|
|
104
123
|
* Refresh the remote-tracking refs so a session branches off the CURRENT state
|
|
105
124
|
* of `origin`, not whatever the local checkout last saw. Best-effort: an offline
|
package/dist/runtime/index.js
CHANGED
|
@@ -116,14 +116,27 @@ function claudeCodeInfo() {
|
|
|
116
116
|
},
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
+
// Memoized CLI probes. `commandAvailable`/`resolveCommandPath`/`probeHelpText`
|
|
120
|
+
// each shell out with a BLOCKING spawnSync, and the runtime catalog that calls
|
|
121
|
+
// them (cliAgentInfo → prefersAcp/acpSupportedByBinary) is rebuilt often — on
|
|
122
|
+
// every advertise and every runtimes.list send. Re-probing per build stalled the
|
|
123
|
+
// event loop for seconds at a time (a synchronous spawn storm). A CLI's presence
|
|
124
|
+
// is effectively constant for the daemon's run, so cache per command for the
|
|
125
|
+
// process lifetime and clear on install (invalidateCliProbeCache).
|
|
126
|
+
const COMMAND_AVAILABLE_CACHE = new Map();
|
|
119
127
|
function commandAvailable(command) {
|
|
120
128
|
if (!command.trim())
|
|
121
129
|
return false;
|
|
130
|
+
const cached = COMMAND_AVAILABLE_CACHE.get(command);
|
|
131
|
+
if (cached !== undefined)
|
|
132
|
+
return cached;
|
|
122
133
|
const result = spawnSync(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [command] : ["-v", command], {
|
|
123
134
|
shell: process.platform !== "win32",
|
|
124
135
|
stdio: "ignore",
|
|
125
136
|
});
|
|
126
|
-
|
|
137
|
+
const available = result.status === 0;
|
|
138
|
+
COMMAND_AVAILABLE_CACHE.set(command, available);
|
|
139
|
+
return available;
|
|
127
140
|
}
|
|
128
141
|
function genericCliInfo() {
|
|
129
142
|
const options = processRuntimeFromEnv();
|
|
@@ -179,7 +192,11 @@ const CLI_AGENT_SPECS = {
|
|
|
179
192
|
// reply to stdout (the TUI needs a real TTY and would hang over a pipe).
|
|
180
193
|
args: ["run"],
|
|
181
194
|
promptMode: "argv",
|
|
182
|
-
|
|
195
|
+
// Supported tier: OpenCode runs on the governed ACP path by default (per-tool
|
|
196
|
+
// Approve/Deny + session/load resume + a real model picker), the same bar Pi,
|
|
197
|
+
// Claude Code, and Codex clear. See `acp` below for the version fallback.
|
|
198
|
+
supportTier: "supported",
|
|
199
|
+
testedVersion: "1.18.13",
|
|
183
200
|
blurb: "The most widely used open-source coding harness (OpenCode CLI).",
|
|
184
201
|
// `opencode run -s <id> "<prompt>"` continues a prior session by its own id
|
|
185
202
|
// (`-s, --session session id to continue`, per `opencode run --help`).
|
|
@@ -195,11 +212,14 @@ const CLI_AGENT_SPECS = {
|
|
|
195
212
|
{ id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro", provider: "google" },
|
|
196
213
|
],
|
|
197
214
|
},
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
215
|
+
// `opencode acp` ("start ACP (Agent Client Protocol) server") drives OpenCode
|
|
216
|
+
// through the governed ProtocolRuntime instead of the one-shot pipe: per-tool
|
|
217
|
+
// Approve/Deny, streaming, `session/load` resume, and `session/set_model`.
|
|
218
|
+
// Validated against opencode 1.18.13, so it is ON by default (`preferred`) —
|
|
219
|
+
// gated on the binary actually listing the `acp` subcommand, so an older
|
|
220
|
+
// OpenCode falls back to the pipe path rather than opening a dead session.
|
|
221
|
+
// Force the pipe path back with BIVY_OPENCODE_ACP=0.
|
|
222
|
+
acp: { args: ["acp"], helpToken: "acp", preferred: true },
|
|
203
223
|
install: { kind: "npm", pkg: "opencode-ai" },
|
|
204
224
|
},
|
|
205
225
|
aider: {
|
|
@@ -857,10 +877,34 @@ function cliThinkingConfig(id) {
|
|
|
857
877
|
// installed binary doesn't actually mention. It never UPGRADES — adding a
|
|
858
878
|
// capability needs the exact arg template, which help text can't safely supply — so
|
|
859
879
|
// probing can only make the catalog MORE honest, never invent a no-op control.
|
|
880
|
+
/**
|
|
881
|
+
* Absolute path of a command on the current PATH, or null when it isn't there.
|
|
882
|
+
* Used to key the help-probe cache: caching by the bare NAME would keep serving a
|
|
883
|
+
* stale answer after the binary behind that name changed (a CLI upgraded or
|
|
884
|
+
* installed while the daemon is running, or a different PATH entry winning).
|
|
885
|
+
* Memoized per command (see COMMAND_AVAILABLE_CACHE) — it spawnSyncs, and is hit
|
|
886
|
+
* on every catalog build.
|
|
887
|
+
*/
|
|
888
|
+
const COMMAND_PATH_CACHE = new Map();
|
|
889
|
+
function resolveCommandPath(command) {
|
|
890
|
+
if (!command.trim())
|
|
891
|
+
return null;
|
|
892
|
+
const cached = COMMAND_PATH_CACHE.get(command);
|
|
893
|
+
if (cached !== undefined)
|
|
894
|
+
return cached;
|
|
895
|
+
const res = spawnSync(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [command] : ["-v", command], {
|
|
896
|
+
shell: process.platform !== "win32",
|
|
897
|
+
encoding: "utf8",
|
|
898
|
+
});
|
|
899
|
+
const resolved = res.status !== 0 ? null : ((res.stdout ?? "").split(/\r?\n/)[0]?.trim() || null);
|
|
900
|
+
COMMAND_PATH_CACHE.set(command, resolved);
|
|
901
|
+
return resolved;
|
|
902
|
+
}
|
|
860
903
|
const HELP_PROBE_CACHE = new Map();
|
|
861
904
|
function probeHelpText(command) {
|
|
862
|
-
|
|
863
|
-
|
|
905
|
+
const key = resolveCommandPath(command) ?? command;
|
|
906
|
+
if (HELP_PROBE_CACHE.has(key))
|
|
907
|
+
return HELP_PROBE_CACHE.get(key) ?? null;
|
|
864
908
|
let text = null;
|
|
865
909
|
try {
|
|
866
910
|
const res = spawnSync(command, ["--help"], { encoding: "utf8", timeout: 4000 });
|
|
@@ -870,9 +914,22 @@ function probeHelpText(command) {
|
|
|
870
914
|
catch {
|
|
871
915
|
text = null;
|
|
872
916
|
}
|
|
873
|
-
HELP_PROBE_CACHE.set(
|
|
917
|
+
HELP_PROBE_CACHE.set(key, text);
|
|
874
918
|
return text;
|
|
875
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Drop every memoized CLI probe (availability, resolved path, --help text). These
|
|
922
|
+
* probes shell out with a blocking spawnSync and are cached for the process
|
|
923
|
+
* lifetime to keep the frequently-rebuilt runtime catalog off the event loop, so a
|
|
924
|
+
* CLI installed/updated mid-run wouldn't otherwise be noticed until a restart.
|
|
925
|
+
* Call this right after Bivy installs a runtime so the next catalog build re-probes
|
|
926
|
+
* and reflects the new binary immediately.
|
|
927
|
+
*/
|
|
928
|
+
export function invalidateCliProbeCache() {
|
|
929
|
+
COMMAND_AVAILABLE_CACHE.clear();
|
|
930
|
+
COMMAND_PATH_CACHE.clear();
|
|
931
|
+
HELP_PROBE_CACHE.clear();
|
|
932
|
+
}
|
|
876
933
|
// A resume template mixes launch flags (`-p`, `--force`) with the resume-specific
|
|
877
934
|
// token(s) (`--resume`, `threads continue`, `-s`, `--restore`, …). Only the latter
|
|
878
935
|
// evidence resume support, so we match on those — otherwise a shared launch flag
|
|
@@ -963,9 +1020,14 @@ function cliAgentInfo(id) {
|
|
|
963
1020
|
// src/harness/mcp-inject.ts + governMcpCall in src/server.ts.
|
|
964
1021
|
capabilities: { toolInterception: acpActive, mcpToolApprovals: acpActive || Boolean(process.env.BIVY_MCP_PROXY), modelSelection, resume, packages: false, fork: false, usageReporting, sessionDiscovery: id === "codex" },
|
|
965
1022
|
supportTier: spec.supportTier ?? (id === "codex" ? "supported" : "experimental"),
|
|
1023
|
+
testedVersion: spec.testedVersion,
|
|
966
1024
|
authOwner: spec.authOwner ?? "agent",
|
|
967
1025
|
notes: installed
|
|
968
|
-
?
|
|
1026
|
+
? acpActive
|
|
1027
|
+
// Promoted to ACP: the description must match the governed path actually in
|
|
1028
|
+
// use, not the pipe path this agent would otherwise take.
|
|
1029
|
+
? `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.`
|
|
1030
|
+
: `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
1031
|
: `${spec.command} was not found on PATH. Install it on this node, then select this agent again.`,
|
|
970
1032
|
install: installed || !installCommand ? undefined : {
|
|
971
1033
|
label: `Install ${spec.displayName}`,
|
|
@@ -980,6 +1042,13 @@ function cliAgentInfo(id) {
|
|
|
980
1042
|
// Approve/Deny card via guardianInterceptor, AND it resumes a prior thread by its
|
|
981
1043
|
// rollout id (thread/resume). Governed + resumable in one runtime supersedes the
|
|
982
1044
|
// exec path, which stays runnable via `BIVY_RUNTIME=codex` for a no-approval flow.
|
|
1045
|
+
/**
|
|
1046
|
+
* The Codex CLI release this adapter was last certified against. Unlike Pi and the
|
|
1047
|
+
* Claude Agent SDK, Codex is an external binary rather than a pinned npm dependency,
|
|
1048
|
+
* so there is no lockfile entry to derive this from — it is bumped deliberately when
|
|
1049
|
+
* the app-server shim is re-validated against a new Codex release.
|
|
1050
|
+
*/
|
|
1051
|
+
const CODEX_TESTED_VERSION = "0.145.0";
|
|
983
1052
|
function codexApprovalsInfo() {
|
|
984
1053
|
const installed = commandAvailable("codex");
|
|
985
1054
|
return {
|
|
@@ -1009,7 +1078,12 @@ function codexApprovalsInfo() {
|
|
|
1009
1078
|
nativeSessionDiscovery: true,
|
|
1010
1079
|
nativeSessionAdoption: true,
|
|
1011
1080
|
},
|
|
1012
|
-
|
|
1081
|
+
// Supported tier: the app-server shim already clears the same bar as Pi and
|
|
1082
|
+
// Claude Code — per-tool Approve/Deny, model selection, thread resume, usage
|
|
1083
|
+
// reporting, and native session discovery/adoption — all over a bidirectional
|
|
1084
|
+
// protocol rather than a one-shot pipe.
|
|
1085
|
+
supportTier: "supported",
|
|
1086
|
+
testedVersion: CODEX_TESTED_VERSION,
|
|
1013
1087
|
authOwner: "agent",
|
|
1014
1088
|
notes: installed
|
|
1015
1089
|
? "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 +1276,50 @@ function acpRuntimeFromEnv(credsDir) {
|
|
|
1202
1276
|
return acpRuntimeOptions({ id: "acp", displayName: process.env.BIVY_ACP_NAME?.trim() || "ACP Agent", command, agentArgs, credsDir });
|
|
1203
1277
|
}
|
|
1204
1278
|
/**
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1279
|
+
* Does the INSTALLED binary actually evidence the agent's ACP mode? A default-on
|
|
1280
|
+
* promotion must never be taken on faith: ACP is a hard switch (the pipe path is
|
|
1281
|
+
* unreachable once a session opens), so a CLI too old to have the subcommand would
|
|
1282
|
+
* otherwise hang and die instead of degrading. We reuse the same cached `--help`
|
|
1283
|
+
* probe the opt-in capability refinement uses, and fail CLOSED — a missing binary
|
|
1284
|
+
* or unreadable help keeps the agent on the honest pipe path.
|
|
1285
|
+
*/
|
|
1286
|
+
function acpSupportedByBinary(id) {
|
|
1287
|
+
const spec = CLI_AGENT_SPECS[id];
|
|
1288
|
+
if (!spec.acp)
|
|
1289
|
+
return false;
|
|
1290
|
+
if (!commandAvailable(spec.command))
|
|
1291
|
+
return false;
|
|
1292
|
+
const help = probeHelpText(spec.command);
|
|
1293
|
+
if (!help)
|
|
1294
|
+
return false;
|
|
1295
|
+
const token = (spec.acp.helpToken ?? spec.acp.args[0] ?? "acp").toLowerCase();
|
|
1296
|
+
return help.includes(token);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Whether a CLI agent should be driven through ACP rather than the one-shot pipe.
|
|
1300
|
+
* Three ways in, in precedence order:
|
|
1301
|
+
* - `BIVY_<ID>_ACP=0` — operator forces the pipe path back (escape hatch).
|
|
1302
|
+
* - `BIVY_<ID>_ACP=1` / `BIVY_PREFER_ACP=1` — operator forces ACP, no probe (they
|
|
1303
|
+
* know their binary; an explicit request shouldn't be second-guessed).
|
|
1304
|
+
* - `spec.acp.preferred` — validated agents are promoted by DEFAULT, but only
|
|
1305
|
+
* when the installed binary evidences the ACP mode (see acpSupportedByBinary).
|
|
1306
|
+
* Still no per-agent code: a spec field plus a flag.
|
|
1307
|
+
*
|
|
1308
|
+
* Both the catalog (cliAgentInfo) and the launch path (makeCliRuntime) call this,
|
|
1309
|
+
* so what the picker advertises and what actually starts cannot disagree.
|
|
1209
1310
|
*/
|
|
1210
1311
|
function prefersAcp(id) {
|
|
1211
|
-
|
|
1312
|
+
const spec = CLI_AGENT_SPECS[id];
|
|
1313
|
+
if (!spec.acp)
|
|
1314
|
+
return false;
|
|
1315
|
+
const override = process.env[`BIVY_${id.toUpperCase()}_ACP`];
|
|
1316
|
+
if (override === "0")
|
|
1317
|
+
return false;
|
|
1318
|
+
if (override === "1" || process.env.BIVY_PREFER_ACP === "1")
|
|
1319
|
+
return true;
|
|
1320
|
+
if (!spec.acp.preferred)
|
|
1212
1321
|
return false;
|
|
1213
|
-
return
|
|
1322
|
+
return acpSupportedByBinary(id);
|
|
1214
1323
|
}
|
|
1215
1324
|
/**
|
|
1216
1325
|
* Resolve the communication mode for a CLI agent. This is deliberately pure so
|
package/dist/runtime/protocol.js
CHANGED
|
@@ -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/dist/secrets.js
CHANGED
|
@@ -183,8 +183,12 @@ export class SecretVault {
|
|
|
183
183
|
if (fs.existsSync(this.keyFile))
|
|
184
184
|
checks.push({ name: "local key file permissions", ok: modeIsPrivate(this.keyFile), detail: this.keyFile });
|
|
185
185
|
checks.push({ name: "1Password CLI", ok: await commandExists("op"), detail: "required for op:// references" });
|
|
186
|
-
checks.push({ name: "GitHub CLI", ok: await commandExists("gh"), detail: "
|
|
187
|
-
|
|
186
|
+
checks.push({ name: "GitHub CLI (optional)", ok: await commandExists("gh"), detail: "optional token shortcut; Bivy connects GitHub itself via `bivy github:connect`" });
|
|
187
|
+
// 1Password and the GitHub CLI are optional shortcuts — a missing one must
|
|
188
|
+
// not fail the vault's health (Bivy connects GitHub itself; op is only for
|
|
189
|
+
// op:// refs). Match on a stable prefix so renaming the label can't silently
|
|
190
|
+
// turn either back into a hard failure.
|
|
191
|
+
return { ok: checks.every((c) => c.ok || c.name.startsWith("1Password CLI") || c.name.startsWith("GitHub CLI")), checks };
|
|
188
192
|
}
|
|
189
193
|
key() {
|
|
190
194
|
try {
|