@bivy/bivy 0.15.0-staging.35 → 0.15.0-staging.37
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/auth-errors.js +9 -0
- package/dist/server.js +38 -19
- package/package.json +1 -1
|
@@ -77,3 +77,12 @@ export function authProviderForSession(runtimeId, modelProvider) {
|
|
|
77
77
|
return provider;
|
|
78
78
|
return undefined;
|
|
79
79
|
}
|
|
80
|
+
/** Convert a provider failure into the safe wire shape consumed by clients.
|
|
81
|
+
* Raw provider text can contain stack frames, paths, or tokens and must never
|
|
82
|
+
* be used as the user-facing auth error. */
|
|
83
|
+
export function classifyModelAuthError(raw, runtimeId, modelProvider) {
|
|
84
|
+
if (!isModelAuthError(raw))
|
|
85
|
+
return null;
|
|
86
|
+
const provider = authProviderForSession(runtimeId, modelProvider);
|
|
87
|
+
return provider ? { kind: "model_auth", provider } : null;
|
|
88
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -34,7 +34,7 @@ import { InMemoryLocationRegistry } from "./runtime/location-registry.js";
|
|
|
34
34
|
import { ControlPlaneSessionLocationRegistry, LayeredSessionLocationRegistry } from "./runtime/control-plane-location.js";
|
|
35
35
|
import { attachAdoptedSessions, classifyAttachFailure } from "./runtime/adoption.js";
|
|
36
36
|
import { createCredentialStore, testProviderCredential } from "./runtime/credentials.js";
|
|
37
|
-
import { isModelAuthError, authProviderForSession } from "./runtime/auth-errors.js";
|
|
37
|
+
import { isModelAuthError, authProviderForSession, classifyModelAuthError } from "./runtime/auth-errors.js";
|
|
38
38
|
import { createCredentialVault, migrateVaultDir } from "./runtime/credential-store.js";
|
|
39
39
|
import { probeAnthropicAccess } from "./runtime/anthropic-preflight.js";
|
|
40
40
|
import { createSessionNamer, fallbackSessionName } from "./session/session-namer.js";
|
|
@@ -930,6 +930,7 @@ function harnessDirFor(record) {
|
|
|
930
930
|
function harnessBeginTurn(record) {
|
|
931
931
|
const dir = harnessDirFor(record);
|
|
932
932
|
record.harnessTurnReady = undefined;
|
|
933
|
+
record.harnessTurnFinished = false;
|
|
933
934
|
if (!dir)
|
|
934
935
|
return;
|
|
935
936
|
const previous = record.workspaceState === "dirty" ? "dirty" : "clean";
|
|
@@ -954,6 +955,14 @@ function harnessBeginTurn(record) {
|
|
|
954
955
|
})();
|
|
955
956
|
}
|
|
956
957
|
/** After a turn, snapshot again and broadcast the structured diff it produced. */
|
|
958
|
+
function finishHarnessTurn(record) {
|
|
959
|
+
if (record.harnessTurnFinished)
|
|
960
|
+
return;
|
|
961
|
+
record.harnessTurnFinished = true;
|
|
962
|
+
void harnessEndTurn(record).finally(() => {
|
|
963
|
+
void replication.onTurnComplete(record.id);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
957
966
|
async function harnessEndTurn(record) {
|
|
958
967
|
const previous = record.workspaceState === "dirty" ? "dirty" : "clean";
|
|
959
968
|
try {
|
|
@@ -1643,11 +1652,14 @@ function stampSessionEvent(payload) {
|
|
|
1643
1652
|
return payload;
|
|
1644
1653
|
}
|
|
1645
1654
|
// Collapse the burst of assistant `message_update`s (one per agent stdout line,
|
|
1646
|
-
// each carrying the FULL text so far)
|
|
1647
|
-
//
|
|
1655
|
+
// each carrying the FULL text so far) to a human-smooth 4 fps. A 16 ms window
|
|
1656
|
+
// still allowed up to 62 cumulative snapshots/second, making a long answer
|
|
1657
|
+
// quadratic and surprisingly expensive over a phone's relay connection. The
|
|
1658
|
+
// latest snapshot supersedes every skipped one, and non-update events flush it
|
|
1659
|
+
// immediately, so 250 ms changes neither final content nor event ordering.
|
|
1648
1660
|
// The coalescer's emit stamps the surviving (latest) update, so only ~one seq is
|
|
1649
|
-
// spent per
|
|
1650
|
-
const SESSION_UPDATE_COALESCE_MS =
|
|
1661
|
+
// spent per window — the ring holds turn-scale events, not every stdout line.
|
|
1662
|
+
const SESSION_UPDATE_COALESCE_MS = 250;
|
|
1651
1663
|
const sessionEvents = new SessionEventCoalescer({
|
|
1652
1664
|
coalesceMs: SESSION_UPDATE_COALESCE_MS,
|
|
1653
1665
|
emit: (payload) => broadcastCoalesced(stampSessionEvent(payload)),
|
|
@@ -2842,7 +2854,7 @@ const RELAY_COMMANDS = {
|
|
|
2842
2854
|
// this the relay client (PWA) is stranded on "Working…" forever with
|
|
2843
2855
|
// only a session.error toast. Clear working so a terminal state reaches it.
|
|
2844
2856
|
clearSessionWorking(record);
|
|
2845
|
-
broadcast({ type: "session.error", sessionId: record.id, error:
|
|
2857
|
+
broadcast({ type: "session.error", sessionId: record.id, error: sessionAgentError(record, error) });
|
|
2846
2858
|
});
|
|
2847
2859
|
},
|
|
2848
2860
|
async "session.new"(msg) {
|
|
@@ -5959,6 +5971,7 @@ async function sessionListRows() {
|
|
|
5959
5971
|
agent: meta?.runtimeId ?? s.agent,
|
|
5960
5972
|
agentName: meta?.agentName ?? s.agentName,
|
|
5961
5973
|
source: rec?.source ?? meta?.source,
|
|
5974
|
+
bivyCreated: Boolean(meta),
|
|
5962
5975
|
forkedFrom: rec?.forkedFrom ?? meta?.forkedFrom,
|
|
5963
5976
|
branch: rec?.worktree?.branch ?? meta?.branch,
|
|
5964
5977
|
sandbox: rec?.sandbox ?? normalizeSandboxTier(meta?.sandbox),
|
|
@@ -7175,6 +7188,11 @@ function actionableAgentError(runtimeId, error) {
|
|
|
7175
7188
|
}
|
|
7176
7189
|
return raw;
|
|
7177
7190
|
}
|
|
7191
|
+
function sessionAgentError(record, error) {
|
|
7192
|
+
const raw = humanizeAgentError(error instanceof Error ? error.message : String(error));
|
|
7193
|
+
return classifyModelAuthError(raw, record.runtimeId, record.session.getCurrentModel()?.provider)
|
|
7194
|
+
?? actionableAgentError(record.runtimeId, error);
|
|
7195
|
+
}
|
|
7178
7196
|
/**
|
|
7179
7197
|
* A turn that ended in a *terminal* model/provider failure the runtime would
|
|
7180
7198
|
* otherwise swallow. `agent_end` carries the turn's messages and whether the
|
|
@@ -7231,7 +7249,7 @@ function attachSessionListeners(record) {
|
|
|
7231
7249
|
policy: sessionRunPolicy,
|
|
7232
7250
|
onNotice: (n) => broadcast({ type: "session.notice", sessionId: record.id, level: n.level, message: n.message }),
|
|
7233
7251
|
onModelChanged: () => broadcast({ type: "model.updated", sessionId: record.id, model: publicModel(record.session.getCurrentModel(), record.session.getCurrentModel()) }),
|
|
7234
|
-
onFailed: (message) => broadcast({ type: "session.error", sessionId: record.id, error: message }),
|
|
7252
|
+
onFailed: (message) => broadcast({ type: "session.error", sessionId: record.id, error: sessionAgentError(record, message) }),
|
|
7235
7253
|
});
|
|
7236
7254
|
}
|
|
7237
7255
|
record.unsubscribe = record.session.subscribe((event) => {
|
|
@@ -7328,9 +7346,14 @@ function attachSessionListeners(record) {
|
|
|
7328
7346
|
transcripts.resolveInlineImages(record);
|
|
7329
7347
|
}
|
|
7330
7348
|
// Durably persist the throttled sidecars at the turn boundary so a crash
|
|
7331
|
-
// loses at most the in-flight turn's UI detail, not the whole turn.
|
|
7332
|
-
|
|
7349
|
+
// loses at most the in-flight turn's UI detail, not the whole turn. A
|
|
7350
|
+
// runtime may keep its agent process alive and never emit agent_end (Pi),
|
|
7351
|
+
// so turn_end is also authoritative for the interactive working state.
|
|
7352
|
+
if (event.type === "turn_end") {
|
|
7333
7353
|
eventLog.flush(record.id);
|
|
7354
|
+
clearSessionWorking(record);
|
|
7355
|
+
finishHarnessTurn(record);
|
|
7356
|
+
}
|
|
7334
7357
|
// AskUserQuestion is intercepted and answered by the daemon's guardian /
|
|
7335
7358
|
// QuestionManager (see guardianInterceptor), which broadcasts
|
|
7336
7359
|
// session.question(.resolved) from its own listeners — runtimes no longer
|
|
@@ -7365,14 +7388,9 @@ function attachSessionListeners(record) {
|
|
|
7365
7388
|
transcripts.clearLiveIntermediate(record.id);
|
|
7366
7389
|
clearSessionWorking(record);
|
|
7367
7390
|
void refreshSessionUsage(record);
|
|
7368
|
-
// Snapshot
|
|
7369
|
-
//
|
|
7370
|
-
|
|
7371
|
-
// (so the shipped frame carries this turn's transcript AND its checkpoint).
|
|
7372
|
-
// Gated on session sync — inert by default.
|
|
7373
|
-
void harnessEndTurn(record).finally(() => {
|
|
7374
|
-
void replication.onTurnComplete(record.id);
|
|
7375
|
-
});
|
|
7391
|
+
// Snapshot/diff at whichever completion boundary arrived first. Pi keeps
|
|
7392
|
+
// its process alive after turn_end; other runtimes also emit agent_end.
|
|
7393
|
+
finishHarnessTurn(record);
|
|
7376
7394
|
// A turn that ended in a terminal model/provider error (e.g. an expired
|
|
7377
7395
|
// credential or a 4xx from the API) otherwise vanished: working cleared,
|
|
7378
7396
|
// no reply, no signal. Surface it as a session-scoped error so the client
|
|
@@ -7434,7 +7452,7 @@ function attachSessionListeners(record) {
|
|
|
7434
7452
|
scheduleAdvertise();
|
|
7435
7453
|
broadcast({ type: "session.failed", sessionId: record.id, failedAt: record.lastFailureAt });
|
|
7436
7454
|
if (messageError)
|
|
7437
|
-
broadcast({ type: "session.error", sessionId: record.id, error:
|
|
7455
|
+
broadcast({ type: "session.error", sessionId: record.id, error: sessionAgentError(record, messageError) });
|
|
7438
7456
|
// If the terminal error is an auth failure (expired key/token → 4xx),
|
|
7439
7457
|
// also raise the sign-in sheet for the failing provider.
|
|
7440
7458
|
maybeSignalAuthRequired(record, turnError);
|
|
@@ -8058,7 +8076,7 @@ async function createSession(workspace = defaultWorkspace, sessionFile, opts = {
|
|
|
8058
8076
|
void refreshSessionUsage(record);
|
|
8059
8077
|
if (makeActive)
|
|
8060
8078
|
active = record;
|
|
8061
|
-
broadcast({ type: "session.created", sessionId, name: record.session.getName(), workspace: sessionWorkspace, sessionFile: record.sessionFile, source: record.source, branch: worktree?.branch, prUrl: record.prUrl, runtimeId: rt.id, agentName: rt.displayName, modelFallbackMessage, bivySession: bivySessionEnvelope(record), capabilities: capabilitiesWithCommands(rt.id, record.session) });
|
|
8079
|
+
broadcast({ type: "session.created", sessionId, name: record.session.getName(), workspace: sessionWorkspace, sessionFile: record.sessionFile, source: record.source, bivyCreated: true, branch: worktree?.branch, prUrl: record.prUrl, runtimeId: rt.id, agentName: rt.displayName, modelFallbackMessage, bivySession: bivySessionEnvelope(record), capabilities: capabilitiesWithCommands(rt.id, record.session) });
|
|
8062
8080
|
void maybeNotifyBivyUpdate();
|
|
8063
8081
|
scheduleAdvertise();
|
|
8064
8082
|
return record;
|
|
@@ -9853,6 +9871,7 @@ app.get("/api/sessions", async (_req, res, next) => {
|
|
|
9853
9871
|
agent: meta?.runtimeId ?? s.agent,
|
|
9854
9872
|
agentName: meta?.agentName ?? s.agentName,
|
|
9855
9873
|
source: rec?.source ?? meta?.source,
|
|
9874
|
+
bivyCreated: Boolean(meta),
|
|
9856
9875
|
forkedFrom: rec?.forkedFrom ?? meta?.forkedFrom,
|
|
9857
9876
|
branch: rec?.worktree?.branch ?? meta?.branch,
|
|
9858
9877
|
prUrl: rec?.prUrl ?? meta?.prUrl,
|
package/package.json
CHANGED