Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d591a90
fix(inference): keep Windows Ollama requests in Docker
ericksoa Sep 1, 2026
97eaa30
Merge branch 'main' into fix/wsl-ollama-model-discovery-10553
ericksoa Sep 1, 2026
a2e4956
fix(inference): complete Windows Ollama transport
ericksoa Sep 1, 2026
a426ab8
fix(inference): persist Windows Ollama cleanup route
ericksoa Sep 1, 2026
057ebc5
fix(inference): preserve destroy cleanup contract
ericksoa Sep 1, 2026
e4cc146
refactor(inference): finalize Ollama route ownership
ericksoa Sep 1, 2026
a0059be
fix(inference): restore accepted Ollama route
ericksoa Sep 1, 2026
4fc9f3d
fix(inference): complete Ollama route recovery
ericksoa Sep 1, 2026
abc65bd
fix(inference): finalize Ollama lifecycle recovery
ericksoa Sep 1, 2026
3dbf08b
fix(tunnel): scope Ollama cleanup completion
ericksoa Sep 1, 2026
71e39e6
fix(inference): complete Ollama route lifecycle
ericksoa Sep 1, 2026
e191f6a
test(inference): require exact Ollama probe URLs
ericksoa Sep 1, 2026
bed0a10
fix(destroy): preserve failed Ollama cleanup recovery
ericksoa Sep 1, 2026
b35166e
test(inference): focus Ollama transport contracts
ericksoa Sep 1, 2026
a15ea79
fix: clear Windows-host Ollama review gaps
ericksoa Sep 1, 2026
7125b6e
fix: isolate every Windows Ollama operation
ericksoa Sep 1, 2026
6ba00d7
test: require exact Windows Ollama URLs
ericksoa Sep 1, 2026
40c869a
fix: preserve peer Ollama model ownership
ericksoa Sep 1, 2026
1307316
refactor: retire Ollama routes through owner
ericksoa Sep 1, 2026
ed3c43a
fix: persist scoped Ollama cleanup retries
ericksoa Sep 1, 2026
9382e36
fix: propagate Ollama cleanup exceptions
ericksoa Sep 1, 2026
715c315
fix(inference): preserve Ollama cleanup recovery
prekshivyas Sep 1, 2026
38a6327
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
prekshivyas Sep 1, 2026
6eb158b
test: allow policy rollback under coverage
ericksoa Sep 1, 2026
aa89007
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
prekshivyas Sep 1, 2026
e7e8582
fix: preserve scoped Ollama cleanup recovery
ericksoa Sep 1, 2026
b2929f5
test: stabilize readiness deadline budget
ericksoa Sep 1, 2026
5686a33
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
prekshivyas Sep 1, 2026
11855ea
test: use readiness clock fix from base
ericksoa Sep 1, 2026
146fbe0
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
prekshivyas Sep 1, 2026
735203b
fix: retain Ollama cleanup when host is unavailable
ericksoa Sep 1, 2026
b6b3464
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
prekshivyas Sep 1, 2026
7e60520
fix: scope final Ollama cleanup to model owners
ericksoa Sep 1, 2026
c394f83
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
ericksoa Sep 1, 2026
7df5c02
fix: isolate Windows Ollama readiness transport
ericksoa Sep 1, 2026
b1c6ac3
refactor: centralize Windows Ollama model discovery
ericksoa Sep 1, 2026
9a48167
fix: keep Ollama lifecycle transitions atomic
ericksoa Sep 1, 2026
d2968fa
Merge remote-tracking branch 'origin/main' into fix/wsl-ollama-model-…
ericksoa Sep 1, 2026
7051d83
test: remove duplicate readiness clock
ericksoa Sep 1, 2026
edcb7f3
Merge remote-tracking branch 'origin/main' into fix/wsl-ollama-model-…
ericksoa Sep 1, 2026
f059cd0
test: prove Ollama unload runs under ownership lock
ericksoa Sep 1, 2026
fac042b
fix: preserve destroy recovery guidance
prekshivyas Sep 1, 2026
a101ffb
fix: complete Windows Ollama transport migration
ericksoa Sep 1, 2026
27be95f
merge: incorporate concurrent Windows Ollama migration
prekshivyas Sep 1, 2026
c3ac1ce
test: pair immutable probe image with fast E2E
ericksoa Sep 1, 2026
284f65a
merge: incorporate concurrent branch updates
prekshivyas Sep 1, 2026
6f3827e
test: expect immutable probe image guidance
ericksoa Sep 1, 2026
301d64b
test: isolate sandbox service tests from Ollama
ericksoa Sep 1, 2026
09b199f
fix: use multi-platform Ollama probe image
ericksoa Sep 2, 2026
afde82a
test: verify probe pin through transport
ericksoa Sep 2, 2026
5142e5c
fix: serialize Ollama route ownership transitions
ericksoa Sep 2, 2026
6032621
test: guard retired Windows sleep path
prekshivyas Sep 2, 2026
1cb5f9d
test: allow bounded Windows readiness waits
ericksoa Sep 2, 2026
e43ae80
test: focus Windows fallback on behavior
prekshivyas Sep 2, 2026
ebc31a2
test: reject subprocess Windows readiness sleeps
ericksoa Sep 2, 2026
f7975ea
test: isolate missing advisor model lookup
ericksoa Sep 2, 2026
2587eff
fix: protect local Ollama route ownership
prekshivyas Sep 2, 2026
da9fbfc
test: verify bounded Windows readiness delays
ericksoa Sep 2, 2026
2b5cd70
Merge remote-tracking branch 'origin/fix/wsl-ollama-model-discovery-1…
ericksoa Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/lib/actions/inference-set.test-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

import { vi } from "vitest";
import type { ValidationResult } from "../inference/local";
import type { AgentConfigTarget } from "../sandbox/config";
import type { ConfigObject, ConfigValue } from "../security/credential-filter";
import type { Session } from "../state/onboard-session";
import type { SandboxEntry } from "../state/registry";
import type { InferenceSetDeps } from "./inference-set";
import type { EnsureHttpsPinRuntimeAdapterFn } from "./inference-set-route-containment";

type LocalValidationResult = ReturnType<InferenceSetDeps["validateLocalProvider"]>;

export const OPENCLAW_TARGET: AgentConfigTarget = {
agentName: "openclaw",
configPath: "/sandbox/.openclaw/openclaw.json",
Expand Down Expand Up @@ -165,7 +166,7 @@ export function createDeps(options: {
session?: Session | null;
openshellStatus?: number;
captureOpenshell?: InferenceSetDeps["captureOpenshell"];
localValidation?: ValidationResult;
localValidation?: LocalValidationResult;
localReachable?: boolean;
contextWindow?: number | null;
shieldsMutable?: boolean;
Expand Down Expand Up @@ -232,7 +233,9 @@ export function createDeps(options: {
}),
appendAuditEntry: vi.fn(),
log: vi.fn(),
validateLocalProvider: vi.fn((): ValidationResult => options.localValidation ?? { ok: true }),
validateLocalProvider: vi.fn(
(): LocalValidationResult => options.localValidation ?? { ok: true },
),
ensureLocalProviderReachable: vi.fn(() => options.localReachable ?? true),
resolveContextWindowForModel: vi.fn((_provider: string, _model: string) =>
options.contextWindow === undefined ? null : options.contextWindow,
Expand Down
95 changes: 85 additions & 10 deletions src/lib/actions/sandbox/agent/ollama-restart-recovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, expect, it, vi } from "vitest";
import { OLLAMA_PORT, OLLAMA_PROXY_PORT } from "../../../core/ports";
import { prepareOllamaApiExecution } from "../../../inference/local";
import {
maybeWarmOllamaAfterDaemonRestart,
type OllamaRestartRecoveryDeps,
Expand Down Expand Up @@ -46,8 +47,23 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
});

it("uses the persisted direct bridge route for both the default probe and warm-up", () => {
const runCaptureImpl = vi.fn((_command: readonly string[]) => JSON.stringify({ models: [] }));
const runCaptureExImpl = vi.fn((_command: string[]) => successfulWarmResult());
const cleanup = vi.fn(() => ({ ok: true as const }));
const prepareDockerEnvironment = () => ({
env: { DOCKER_CONFIG: "/tmp/credential-free-docker" },
isolatedCredentialConfig: true,
cleanup,
});
const runCaptureImpl = vi.fn(
(_command: readonly string[], options?: { env?: NodeJS.ProcessEnv }) =>
options?.env?.DOCKER_CONFIG === "/tmp/credential-free-docker"
? JSON.stringify({ models: [] })
: "",
);
const runCaptureExImpl = vi.fn((_command: string[], options?: { env?: NodeJS.ProcessEnv }) =>
options?.env?.DOCKER_CONFIG === "/tmp/credential-free-docker"
? successfulWarmResult()
: { stdout: "", exitCode: 1, timedOut: false },
);

expect(
maybeWarmOllamaAfterDaemonRestart(
Expand All @@ -56,21 +72,37 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
model: "qwen3.6:35b",
endpointUrl: `http://host.openshell.internal:${OLLAMA_PORT}/v1`,
},
{ runCaptureImpl, runCaptureExImpl },
{
runCaptureImpl,
runCaptureExImpl,
prepareDockerEnvironment,
prepareOllamaApiExecution: (command, host, options) =>
prepareOllamaApiExecution(command, host, {
...options,
prepareDockerEnvironment,
}),
},
),
).toEqual({ kind: "warmed", ok: true, timedOut: false });

expect(getCommandUrl(runCaptureImpl.mock.calls[0][0])).toBe(
`http://host.docker.internal:${OLLAMA_PORT}/api/ps`,
);
expect(runCaptureImpl.mock.calls[0][0][0]).toBe("docker");
expect(getCommandUrl(runCaptureExImpl.mock.calls[0][0])).toBe(
`http://host.docker.internal:${OLLAMA_PORT}/api/generate`,
);
expect(runCaptureExImpl.mock.calls[0][0][0]).toBe("docker");
expect(getCommandBody(runCaptureExImpl.mock.calls[0][0])).toMatchObject({
model: "qwen3.6:35b",
stream: false,
think: false,
});
expect(runCaptureImpl.mock.calls[0][1]?.env?.DOCKER_CONFIG).toBe("/tmp/credential-free-docker");
expect(runCaptureExImpl.mock.calls[0][1]?.env?.DOCKER_CONFIG).toBe(
"/tmp/credential-free-docker",
);
expect(cleanup).toHaveBeenCalledTimes(2);
});

it("maps an auth-proxy route back to host loopback", () => {
Expand All @@ -89,9 +121,11 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
expect(getCommandUrl(runCaptureImpl.mock.calls[0][0])).toBe(
`http://127.0.0.1:${OLLAMA_PORT}/api/ps`,
);
expect(runCaptureImpl.mock.calls[0][0][0]).toBe("curl");
expect(getCommandUrl(runCaptureExImpl.mock.calls[0][0])).toBe(
`http://127.0.0.1:${OLLAMA_PORT}/api/generate`,
);
expect(runCaptureExImpl.mock.calls[0][0][0]).toBe("curl");
});

it("falls back to an allowlisted host instead of probing an arbitrary registry URL", () => {
Expand Down Expand Up @@ -182,7 +216,14 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
}),
},
),
).toEqual({ kind: "warmed", ok: false, timedOut: true, reason: "timeout" });
).toEqual({
kind: "warmed",
ok: false,
timedOut: true,
reason: "timeout",
endpoint: "http://127.0.0.1:11434",
detail: "warm-up exceeded 300 seconds",
});
});

it("does not treat an exit-zero Ollama error body as a successful warm-up", () => {
Expand All @@ -199,7 +240,14 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
}),
},
),
).toEqual({ kind: "warmed", ok: false, timedOut: false, reason: "ollama-error" });
).toMatchObject({
kind: "warmed",
ok: false,
timedOut: false,
reason: "ollama-error",
endpoint: "http://127.0.0.1:11434",
detail: expect.stringContaining("model not found"),
});
});

it("reports an endpoint that no longer holds the model instead of a warm failure (#9455)", () => {
Expand Down Expand Up @@ -228,7 +276,7 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
endpoint: `http://host.docker.internal:${OLLAMA_PORT}`,
inventoryLabel: "llama3.2:1b",
});
expect(probeModelInventory).toHaveBeenCalledWith("host.docker.internal", undefined);
expect(probeModelInventory).toHaveBeenCalledWith("host.docker.internal", expect.any(Function));
});

it("keeps the warm failure when the daemon does hold the model (#9455)", () => {
Expand All @@ -245,7 +293,14 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
}),
},
),
).toEqual({ kind: "warmed", ok: false, timedOut: false, reason: "ollama-error" });
).toMatchObject({
kind: "warmed",
ok: false,
timedOut: false,
reason: "ollama-error",
endpoint: "http://127.0.0.1:11434",
detail: expect.stringContaining("runner stopped unexpectedly"),
});
});

it("accepts a completed thinking-only response from a thinking model", () => {
Expand Down Expand Up @@ -278,7 +333,13 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
runCaptureExImpl: () => ({ stdout, exitCode: 0, timedOut: false }),
},
),
).toEqual({ kind: "warmed", ok: false, timedOut: false, reason: "invalid-response" });
).toMatchObject({
kind: "warmed",
ok: false,
timedOut: false,
reason: "invalid-response",
endpoint: "http://127.0.0.1:11434",
});
});

it("reports a non-zero warm command exit", () => {
Expand All @@ -290,7 +351,14 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {
runCaptureExImpl: () => ({ stdout: "", exitCode: 7, timedOut: false }),
},
),
).toEqual({ kind: "warmed", ok: false, timedOut: false, reason: "command-failed" });
).toEqual({
kind: "warmed",
ok: false,
timedOut: false,
reason: "command-failed",
endpoint: "http://127.0.0.1:11434",
detail: "warm-up exited 7",
});
});

it("reports a warm process spawn failure without throwing", () => {
Expand All @@ -303,6 +371,13 @@ describe("maybeWarmOllamaAfterDaemonRestart", () => {

expect(
maybeWarmOllamaAfterDaemonRestart({ provider: "ollama-local", model: "qwen3.6:35b" }, deps),
).toEqual({ kind: "warmed", ok: false, timedOut: false, reason: "spawn-failed" });
).toEqual({
kind: "warmed",
ok: false,
timedOut: false,
reason: "spawn-failed",
endpoint: "http://127.0.0.1:11434",
detail: "spawn failed",
});
});
});
Loading
Loading