@bivy/bivy 0.7.0-staging.104 → 0.7.0-staging.106
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/dist/runtime/index.js +36 -4
- package/dist/server.js +44 -6
- package/package.json +1 -1
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();
|
|
@@ -869,17 +882,23 @@ function cliThinkingConfig(id) {
|
|
|
869
882
|
* Used to key the help-probe cache: caching by the bare NAME would keep serving a
|
|
870
883
|
* stale answer after the binary behind that name changed (a CLI upgraded or
|
|
871
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.
|
|
872
887
|
*/
|
|
888
|
+
const COMMAND_PATH_CACHE = new Map();
|
|
873
889
|
function resolveCommandPath(command) {
|
|
874
890
|
if (!command.trim())
|
|
875
891
|
return null;
|
|
892
|
+
const cached = COMMAND_PATH_CACHE.get(command);
|
|
893
|
+
if (cached !== undefined)
|
|
894
|
+
return cached;
|
|
876
895
|
const res = spawnSync(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [command] : ["-v", command], {
|
|
877
896
|
shell: process.platform !== "win32",
|
|
878
897
|
encoding: "utf8",
|
|
879
898
|
});
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
return
|
|
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;
|
|
883
902
|
}
|
|
884
903
|
const HELP_PROBE_CACHE = new Map();
|
|
885
904
|
function probeHelpText(command) {
|
|
@@ -898,6 +917,19 @@ function probeHelpText(command) {
|
|
|
898
917
|
HELP_PROBE_CACHE.set(key, text);
|
|
899
918
|
return text;
|
|
900
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
|
+
}
|
|
901
933
|
// A resume template mixes launch flags (`-p`, `--force`) with the resume-specific
|
|
902
934
|
// token(s) (`--resume`, `threads continue`, `-s`, `--restore`, …). Only the latter
|
|
903
935
|
// evidence resume support, so we match on those — otherwise a shared launch flag
|
package/dist/server.js
CHANGED
|
@@ -8,7 +8,7 @@ import { randomUUID, randomBytes, timingSafeEqual, createHash } from "node:crypt
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import express from "express";
|
|
10
10
|
import { WebSocketServer, WebSocket } from "ws";
|
|
11
|
-
import { listRuntimes, catalogRuntimes, cliInstallSpec, isCliAgentId } from "./runtime/index.js";
|
|
11
|
+
import { listRuntimes, catalogRuntimes, cliInstallSpec, invalidateCliProbeCache, isCliAgentId } from "./runtime/index.js";
|
|
12
12
|
import { createRunPolicy } from "./policy/run-policy.js";
|
|
13
13
|
import { DEFAULT_BACKOFF } from "./policy/ruleset.js";
|
|
14
14
|
import { SessionRerouteController } from "./policy/session-reroute.js";
|
|
@@ -3217,6 +3217,9 @@ const RELAY_COMMANDS = {
|
|
|
3217
3217
|
const before = runtimeList().find((runtime) => runtime.id === spec.id);
|
|
3218
3218
|
if (before?.status !== "available")
|
|
3219
3219
|
await runInstallCommand(spec);
|
|
3220
|
+
// The just-installed binary changes what the CLI probes would report, so drop
|
|
3221
|
+
// their (process-lifetime) cache and let the catalog below re-probe it.
|
|
3222
|
+
invalidateCliProbeCache();
|
|
3220
3223
|
const activeAgent = active?.runtimeId ?? defaultRuntimeId;
|
|
3221
3224
|
const runtimes = runtimeList(activeAgent);
|
|
3222
3225
|
relay?.sendEvent({ type: "runtime.install.done", id: spec.id, runtimes });
|
|
@@ -4345,6 +4348,12 @@ function startModelAuthWatcher() {
|
|
|
4345
4348
|
let sessionAdvertiseTarget;
|
|
4346
4349
|
let advertiseTimer;
|
|
4347
4350
|
let advertiseResyncTimer;
|
|
4351
|
+
// Only one replace-all session advert may be in flight. If an older snapshot
|
|
4352
|
+
// (still containing a just-deleted/pruned session) completes after a newer one,
|
|
4353
|
+
// the control plane resurrects that row. Changes arriving during a request set
|
|
4354
|
+
// this flag and are sent immediately after it completes, in order.
|
|
4355
|
+
let advertiseRunning = false;
|
|
4356
|
+
let advertiseAgain = false;
|
|
4348
4357
|
// How often the node re-affirms it's online to the control plane. Kept well
|
|
4349
4358
|
// under the control plane's NODE_ONLINE_TTL_MS (90s) so a missed beat or two
|
|
4350
4359
|
// doesn't flap a healthy node's status.
|
|
@@ -4575,11 +4584,17 @@ async function advertiseSessions() {
|
|
|
4575
4584
|
: [];
|
|
4576
4585
|
return {
|
|
4577
4586
|
sessionId: s.id,
|
|
4578
|
-
|
|
4579
|
-
|
|
4587
|
+
// Failures (including exhausted credits/rate limits) are outcomes to
|
|
4588
|
+
// review, not blocking questions that keep saying "Needs your response".
|
|
4589
|
+
// Only a still-pending approval/question owns that status.
|
|
4590
|
+
status: pendingApproval ? "needs_action" : (record ? (sessionBusy(record) ? "working" : "idle") : "saved"),
|
|
4591
|
+
needsAction: pendingApproval,
|
|
4580
4592
|
source: record?.source || meta?.source,
|
|
4581
4593
|
titleEnc: name ? relay.sealString(name) : undefined,
|
|
4582
4594
|
branch: record?.worktree?.branch || meta?.branch,
|
|
4595
|
+
// This is activity time, not advert receive time. A daemon restart/full
|
|
4596
|
+
// resync must not make every historical row appear freshly updated.
|
|
4597
|
+
updatedAt: isoFrom(record?.lastTouchedAt ?? meta?.lastActivityAt ?? meta?.updatedAt ?? s.modified),
|
|
4583
4598
|
agentServiceAddress,
|
|
4584
4599
|
githubIssueUrl: record?.githubIssueUrl,
|
|
4585
4600
|
prUrl: record?.prUrl,
|
|
@@ -4597,16 +4612,39 @@ async function advertiseSessions() {
|
|
|
4597
4612
|
// best effort; the periodic resync and the next change will retry
|
|
4598
4613
|
}
|
|
4599
4614
|
}
|
|
4600
|
-
/** Debounced advertise — many session events collapse into one
|
|
4615
|
+
/** Debounced, serialized advertise — many session events collapse into one
|
|
4616
|
+
* POST, and replace-all snapshots can never complete out of order. */
|
|
4601
4617
|
function scheduleAdvertise() {
|
|
4602
|
-
if (!sessionAdvertiseTarget
|
|
4618
|
+
if (!sessionAdvertiseTarget)
|
|
4619
|
+
return;
|
|
4620
|
+
if (advertiseRunning) {
|
|
4621
|
+
advertiseAgain = true;
|
|
4622
|
+
return;
|
|
4623
|
+
}
|
|
4624
|
+
if (advertiseTimer)
|
|
4603
4625
|
return;
|
|
4604
4626
|
advertiseTimer = setTimeout(() => {
|
|
4605
4627
|
advertiseTimer = undefined;
|
|
4606
|
-
void
|
|
4628
|
+
void drainSessionAdverts();
|
|
4607
4629
|
}, 1000);
|
|
4608
4630
|
advertiseTimer.unref?.();
|
|
4609
4631
|
}
|
|
4632
|
+
async function drainSessionAdverts() {
|
|
4633
|
+
if (advertiseRunning) {
|
|
4634
|
+
advertiseAgain = true;
|
|
4635
|
+
return;
|
|
4636
|
+
}
|
|
4637
|
+
advertiseRunning = true;
|
|
4638
|
+
try {
|
|
4639
|
+
do {
|
|
4640
|
+
advertiseAgain = false;
|
|
4641
|
+
await advertiseSessions();
|
|
4642
|
+
} while (advertiseAgain);
|
|
4643
|
+
}
|
|
4644
|
+
finally {
|
|
4645
|
+
advertiseRunning = false;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4610
4648
|
let githubPoller;
|
|
4611
4649
|
/**
|
|
4612
4650
|
* Serialize all work for one issue (keyed by its `issue:owner/repo#N` source).
|
package/package.json
CHANGED