docs(sessions): document background catalog refresh and contain offline env window

Finding 6: serialize createOfflineModelRuntime so overlapping calls cannot
interleave their PI_OFFLINE save/restore pairs and leave the process offline,
and name the process-wide visibility of that window in the docblock.

Finding 7: assert the offline construction through the public refresh seam via
reloadConfig() — the request path that regressed — instead of reading upstream's
private modelNetworkEnabled field.

Finding 8.4/8.5: document the background provider-catalog refresh in
docs/config.md and docs/config.html (cadence, timeout, single retry, offline
opt-out via PI_WEB_OFFLINE / PI_OFFLINE only), and update the changeset to
match the behavior after the earlier fixes.
This commit is contained in:
Federico Jaramillo Martinez
2026-07-25 13:07:02 +02:00
parent c5af390ab9
commit 90dd7ce401
5 changed files with 132 additions and 10 deletions
+33 -7
View File
@@ -471,14 +471,16 @@ describe("AuthService", () => {
});
describe("createModelRuntimeForAgentDir", () => {
it("disables runtime-owned network refreshes so request paths stay local", async () => {
// The stall fix relies on this construction-time flag: reloadConfig(),
// login(), and logout() refresh with allowNetwork = modelNetworkEnabled.
// Asserting the private field is proportionate here because the flag is
// exactly the contract this change depends on.
it("keeps runtime-owned refreshes local so request paths cannot stall", async () => {
// reloadConfig() is the request-path call site that regressed: it refreshes
// with allowNetwork = the construction-time network flag and no abort signal.
const agentDir = await tempAgentDir();
const runtime = await createModelRuntimeForAgentDir(agentDir);
expect(Reflect.get(runtime, "modelNetworkEnabled")).toBe(false);
const refresh = vi.spyOn(runtime, "refresh");
await runtime.reloadConfig();
expect(refresh).toHaveBeenCalledWith({ allowNetwork: false });
});
it("restores a previously set PI_OFFLINE after runtime creation", async () => {
@@ -495,12 +497,36 @@ describe("createModelRuntimeForAgentDir", () => {
try {
const agentDir = await tempAgentDir();
const runtime = await createModelRuntimeForAgentDir(agentDir);
expect(Reflect.get(runtime, "modelNetworkEnabled")).toBe(false);
const refresh = vi.spyOn(runtime, "refresh");
await runtime.reloadConfig();
expect(refresh).toHaveBeenCalledWith({ allowNetwork: false });
expect(process.env["PI_OFFLINE"]).toBeUndefined();
} finally {
if (previous !== undefined) process.env["PI_OFFLINE"] = previous;
}
});
it("restores PI_OFFLINE when creations overlap, because the env windows are serialized", async () => {
vi.unstubAllEnvs();
const previous = process.env["PI_OFFLINE"];
delete process.env["PI_OFFLINE"];
try {
const dirs = await Promise.all([tempAgentDir(), tempAgentDir(), tempAgentDir()]);
const runtimes = await Promise.all(dirs.map((dir) => createModelRuntimeForAgentDir(dir)));
// Interleaved save/restore pairs would leave PI_OFFLINE set process-wide.
expect(process.env["PI_OFFLINE"]).toBeUndefined();
for (const runtime of runtimes) {
const refresh = vi.spyOn(runtime, "refresh");
await runtime.reloadConfig();
expect(refresh).toHaveBeenCalledWith({ allowNetwork: false });
}
} finally {
if (previous !== undefined) process.env["PI_OFFLINE"] = previous;
}
});
});
async function createAuthService(seed: Record<string, Credential> = {}, logger?: AuthServiceLogger) {