mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
Merge remote-tracking branch 'origin/main' into meow/diagnostic-liveness-stability
This commit is contained in:
@@ -169,21 +169,22 @@ describe("ACP translator event ledger replay", () => {
|
||||
|
||||
await secondAgent.loadSession(createLoadSessionRequest(created.sessionId));
|
||||
|
||||
expect(secondRequestMock).not.toHaveBeenCalledWith("sessions.get", expect.anything());
|
||||
expect(secondRequestMock.mock.calls.map((call) => call[0])).not.toContain("sessions.get");
|
||||
const replayedUpdates = secondConnection.__sessionUpdateMock.mock.calls.map(
|
||||
(call) => call[0]?.update,
|
||||
);
|
||||
const replayedUpdateTypes = replayedUpdates.map((update) => update?.sessionUpdate);
|
||||
expect(replayedUpdateTypes).toEqual(
|
||||
expect.arrayContaining([
|
||||
"session_info_update",
|
||||
"available_commands_update",
|
||||
"user_message_chunk",
|
||||
"tool_call",
|
||||
"tool_call_update",
|
||||
"agent_message_chunk",
|
||||
]),
|
||||
);
|
||||
expect(replayedUpdateTypes).toEqual([
|
||||
"session_info_update",
|
||||
"available_commands_update",
|
||||
"user_message_chunk",
|
||||
"tool_call",
|
||||
"tool_call_update",
|
||||
"agent_message_chunk",
|
||||
"session_info_update",
|
||||
"session_info_update",
|
||||
"available_commands_update",
|
||||
]);
|
||||
expect(replayedUpdates).toContainEqual({
|
||||
sessionUpdate: "user_message_chunk",
|
||||
content: { type: "text", text: "Question" },
|
||||
@@ -223,13 +224,21 @@ describe("ACP translator event ledger replay", () => {
|
||||
|
||||
await listedAgent.loadSession(createLoadSessionRequest(firstSession.sessionKey));
|
||||
|
||||
expect(listedRequestMock).not.toHaveBeenCalledWith("sessions.get", expect.anything());
|
||||
expect(listedRequestMock.mock.calls.map((call) => call[0])).not.toContain("sessions.get");
|
||||
const listedReplayTypes = listedConnection.__sessionUpdateMock.mock.calls.map(
|
||||
(call) => call[0]?.update?.sessionUpdate,
|
||||
);
|
||||
expect(listedReplayTypes).toEqual(
|
||||
expect.arrayContaining(["user_message_chunk", "tool_call", "agent_message_chunk"]),
|
||||
);
|
||||
expect(listedReplayTypes).toEqual([
|
||||
"session_info_update",
|
||||
"available_commands_update",
|
||||
"user_message_chunk",
|
||||
"tool_call",
|
||||
"tool_call_update",
|
||||
"agent_message_chunk",
|
||||
"session_info_update",
|
||||
"session_info_update",
|
||||
"available_commands_update",
|
||||
]);
|
||||
|
||||
const listedPrompt = listedAgent.prompt(
|
||||
createPromptRequest(firstSession.sessionKey, "Follow-up"),
|
||||
@@ -265,7 +274,7 @@ describe("ACP translator event ledger replay", () => {
|
||||
).toHaveLength(2);
|
||||
await expect(
|
||||
eventLedger.readReplayBySessionId({ sessionId: firstSession.sessionKey }),
|
||||
).resolves.toMatchObject({ complete: false });
|
||||
).resolves.toEqual({ complete: false, events: [] });
|
||||
|
||||
firstSessionStore.clearAllSessionsForTest();
|
||||
});
|
||||
|
||||
@@ -574,9 +574,9 @@ describe("followup queue collect routing", () => {
|
||||
await done.promise;
|
||||
|
||||
expect(calls.map((call) => call.prompt)).toEqual([
|
||||
expect.stringContaining("first"),
|
||||
expect.stringContaining("second"),
|
||||
expect.stringContaining("third"),
|
||||
"[Queued messages while agent was busy]\n\n---\nQueued #1 (from A)\nfirst",
|
||||
"[Queued messages while agent was busy]\n\n---\nQueued #1 (from Owner)\nsecond",
|
||||
"[Queued messages while agent was busy]\n\n---\nQueued #1 (from A)\nthird",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -814,8 +814,8 @@ describe("followup queue collect routing", () => {
|
||||
expect(guestAttempts).toHaveLength(1);
|
||||
expect(ownerAttempts).toHaveLength(2);
|
||||
expect(successfulCalls.map((call) => call.prompt)).toEqual([
|
||||
expect.stringContaining("guest message"),
|
||||
expect.stringContaining("owner message"),
|
||||
"[Queued messages while agent was busy]\n\n---\nQueued #1 (from Guest)\nguest message",
|
||||
"[Queued messages while agent was busy]\n\n---\nQueued #1 (from Owner)\nowner message",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("completion-cli write-state", () => {
|
||||
await program.parseAsync(["completion", "--write-state"], { from: "user" });
|
||||
|
||||
const cacheDir = path.join(stateDir, "completions");
|
||||
expect((await fs.readdir(cacheDir)).sort()).toEqual([
|
||||
expect((await fs.readdir(cacheDir)).toSorted()).toEqual([
|
||||
"openclaw.bash",
|
||||
"openclaw.fish",
|
||||
"openclaw.ps1",
|
||||
@@ -137,7 +137,7 @@ describe("completion-cli write-state", () => {
|
||||
[program, "qa", process.argv, { purpose: "completion" }],
|
||||
]);
|
||||
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
|
||||
expect((await fs.readdir(path.join(stateDir, "completions"))).sort()).toEqual([
|
||||
expect((await fs.readdir(path.join(stateDir, "completions"))).toSorted()).toEqual([
|
||||
"openclaw.bash",
|
||||
"openclaw.fish",
|
||||
"openclaw.ps1",
|
||||
|
||||
@@ -570,15 +570,16 @@ describe("runCli exit behavior", () => {
|
||||
["tools", ["node", "openclaw", "tools", "effective"]],
|
||||
])("keeps reserved %s command roots out of plugin command discovery", async (_name, argv) => {
|
||||
const parseAsync = vi.fn().mockResolvedValueOnce(undefined);
|
||||
buildProgramMock.mockReturnValueOnce({
|
||||
const program = {
|
||||
commands: [],
|
||||
parseAsync,
|
||||
});
|
||||
};
|
||||
buildProgramMock.mockReturnValueOnce(program);
|
||||
|
||||
await runCli(argv);
|
||||
|
||||
expect(startProxyMock).not.toHaveBeenCalled();
|
||||
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), argv[2], argv);
|
||||
expect(registerSubCliByNameMock.mock.calls).toEqual([[program, argv[2], argv]]);
|
||||
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
|
||||
expect(parseAsync).toHaveBeenCalledWith(argv);
|
||||
});
|
||||
@@ -801,47 +802,41 @@ describe("runCli exit behavior", () => {
|
||||
|
||||
it("swallows Commander parse exits after recording the exit code", async () => {
|
||||
const exitCode = process.exitCode;
|
||||
buildProgramMock.mockReturnValueOnce({
|
||||
const program = {
|
||||
commands: [{ name: () => "status" }],
|
||||
parseAsync: vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(
|
||||
new CommanderError(1, "commander.excessArguments", "too many arguments for 'status'"),
|
||||
),
|
||||
});
|
||||
};
|
||||
buildProgramMock.mockReturnValueOnce(program);
|
||||
|
||||
await expect(runCli(["node", "openclaw", "status"])).resolves.toBeUndefined();
|
||||
|
||||
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), "status", [
|
||||
"node",
|
||||
"openclaw",
|
||||
"status",
|
||||
expect(registerSubCliByNameMock.mock.calls).toEqual([
|
||||
[program, "status", ["node", "openclaw", "status"]],
|
||||
]);
|
||||
expect(process.exitCode).toBe(1);
|
||||
process.exitCode = exitCode;
|
||||
});
|
||||
|
||||
it("loads the real primary command before rendering command help", async () => {
|
||||
buildProgramMock.mockReturnValueOnce({
|
||||
const program = {
|
||||
commands: [{ name: () => "doctor" }],
|
||||
parseAsync: vi.fn().mockResolvedValueOnce(undefined),
|
||||
});
|
||||
};
|
||||
buildProgramMock.mockReturnValueOnce(program);
|
||||
const ctx = { programVersion: "0.0.0-test" };
|
||||
getProgramContextMock.mockReturnValueOnce(ctx as never);
|
||||
|
||||
await runCli(["node", "openclaw", "doctor", "--help"]);
|
||||
|
||||
expect(registerCoreCliByNameMock).toHaveBeenCalledWith(expect.anything(), ctx, "doctor", [
|
||||
"node",
|
||||
"openclaw",
|
||||
"doctor",
|
||||
"--help",
|
||||
expect(registerCoreCliByNameMock.mock.calls).toEqual([
|
||||
[program, ctx, "doctor", ["node", "openclaw", "doctor", "--help"]],
|
||||
]);
|
||||
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), "doctor", [
|
||||
"node",
|
||||
"openclaw",
|
||||
"doctor",
|
||||
"--help",
|
||||
expect(registerSubCliByNameMock.mock.calls).toEqual([
|
||||
[program, "doctor", ["node", "openclaw", "doctor", "--help"]],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -907,10 +902,9 @@ describe("runCli exit behavior", () => {
|
||||
code: "EHOSTUNREACH",
|
||||
});
|
||||
expect(handler(hostUnreachable)).toBeUndefined();
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
"[openclaw] Non-fatal uncaught exception (continuing):",
|
||||
expect.stringContaining("EHOSTUNREACH"),
|
||||
);
|
||||
expect(consoleWarnSpy.mock.calls).toEqual([
|
||||
["[openclaw] Non-fatal uncaught exception (continuing):", hostUnreachable.stack],
|
||||
]);
|
||||
expect(restoreTerminalStateMock).not.toHaveBeenCalled();
|
||||
expect(exitSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
|
||||
@@ -124,10 +124,8 @@ describe("gateway stability lane", () => {
|
||||
expect(lastSeq).toBeGreaterThan(firstSeq);
|
||||
expect(snapshot.summary.byType["diagnostic.memory.sample"]).toBeGreaterThan(0);
|
||||
expect(snapshot.summary.byType["message.queued"]).toBeGreaterThan(0);
|
||||
expect(snapshot.summary.memory).toMatchObject({
|
||||
maxRssBytes: maxSyntheticRssBytes,
|
||||
pressureCount: 0,
|
||||
});
|
||||
expect(snapshot.summary.memory?.maxRssBytes).toBe(maxSyntheticRssBytes);
|
||||
expect(snapshot.summary.memory?.pressureCount).toBe(0);
|
||||
expect(snapshot.summary.memory?.maxHeapUsedBytes).toBeLessThan(96 * MB);
|
||||
expect(snapshot.summary.payloadLarge?.chunked).toBeGreaterThan(0);
|
||||
expect(snapshot.summary.payloadLarge?.bySurface["gateway.stability.probe"]).toBeGreaterThan(0);
|
||||
|
||||
@@ -58,10 +58,11 @@ describe("openresponses phase support", () => {
|
||||
phase: "commentary",
|
||||
status: "completed",
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
type: "message",
|
||||
id: "msg_commentary",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Checking logs." }],
|
||||
phase: "commentary",
|
||||
status: "completed",
|
||||
});
|
||||
@@ -73,10 +74,11 @@ describe("openresponses phase support", () => {
|
||||
phase: "final_answer",
|
||||
status: "completed",
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
type: "message",
|
||||
id: "msg_final",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Root cause found." }],
|
||||
phase: "final_answer",
|
||||
status: "completed",
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type StartGatewayDiscovery = typeof import("./server-discovery-runtime.js").startGatewayDiscovery;
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getMachineDisplayName: vi.fn(async () => "Test Machine"),
|
||||
startGatewayDiscovery: vi.fn(async () => ({ bonjourStop: null })),
|
||||
startGatewayDiscovery: vi.fn<StartGatewayDiscovery>(async () => ({ bonjourStop: null })),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/machine-name.js", () => ({
|
||||
@@ -87,15 +89,15 @@ describe("startGatewayEarlyRuntime", () => {
|
||||
}),
|
||||
).resolves.toBe(stop);
|
||||
|
||||
expect(mocks.startGatewayDiscovery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
machineDisplayName: "Test Machine",
|
||||
port: 19_001,
|
||||
gatewayTls: { enabled: true, fingerprintSha256: "abc123" },
|
||||
tailscaleMode: "serve",
|
||||
mdnsMode: "full",
|
||||
gatewayDiscoveryServices: [service],
|
||||
}),
|
||||
);
|
||||
const [discoveryParams] = mocks.startGatewayDiscovery.mock.calls.at(-1) ?? [];
|
||||
if (discoveryParams === undefined) {
|
||||
throw new Error("Expected gateway discovery to start");
|
||||
}
|
||||
expect(discoveryParams.machineDisplayName).toBe("Test Machine");
|
||||
expect(discoveryParams.port).toBe(19_001);
|
||||
expect(discoveryParams.gatewayTls).toEqual({ enabled: true, fingerprintSha256: "abc123" });
|
||||
expect(discoveryParams.tailscaleMode).toBe("serve");
|
||||
expect(discoveryParams.mdnsMode).toBe("full");
|
||||
expect(discoveryParams.gatewayDiscoveryServices).toEqual([service]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,6 +36,19 @@ vi.mock("../agents/pi-embedded-runner/runtime.js", () => ({
|
||||
let prewarmConfiguredPrimaryModel: typeof import("./server-startup-post-attach.js").__testing.prewarmConfiguredPrimaryModel;
|
||||
let shouldSkipStartupModelPrewarm: typeof import("./server-startup-post-attach.js").__testing.shouldSkipStartupModelPrewarm;
|
||||
|
||||
function expectModelsJsonPrewarmCall(cfg: OpenClawConfig) {
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledTimes(1);
|
||||
const [calledConfig, agentDir, options] = ensureOpenClawModelsJsonMock.mock.calls[0] ?? [];
|
||||
expect(calledConfig).toBe(cfg);
|
||||
expect(agentDir).toBe("/tmp/agent");
|
||||
expect(options).toEqual({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
providerDiscoveryProviderIds: ["openai-codex"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
providerDiscoveryEntriesOnly: true,
|
||||
});
|
||||
}
|
||||
|
||||
describe("gateway startup primary model warmup", () => {
|
||||
beforeAll(async () => {
|
||||
({
|
||||
@@ -66,16 +79,7 @@ describe("gateway startup primary model warmup", () => {
|
||||
log: { warn: vi.fn() },
|
||||
});
|
||||
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(
|
||||
cfg,
|
||||
"/tmp/agent",
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
providerDiscoveryProviderIds: ["openai-codex"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
providerDiscoveryEntriesOnly: true,
|
||||
}),
|
||||
);
|
||||
expectModelsJsonPrewarmCall(cfg);
|
||||
expect(piModelModuleLoadedMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -163,16 +167,7 @@ describe("gateway startup primary model warmup", () => {
|
||||
log: { warn: vi.fn() },
|
||||
});
|
||||
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(
|
||||
cfg,
|
||||
"/tmp/agent",
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
providerDiscoveryProviderIds: ["openai-codex"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
providerDiscoveryEntriesOnly: true,
|
||||
}),
|
||||
);
|
||||
expectModelsJsonPrewarmCall(cfg);
|
||||
expect(piModelModuleLoadedMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -69,22 +69,19 @@ test("sessions.create stores dashboard session model and parent linkage, and cre
|
||||
}
|
||||
>;
|
||||
const key = created.payload?.key as string;
|
||||
expect(rawStore[key]).toMatchObject({
|
||||
sessionId: created.payload?.sessionId,
|
||||
label: "Dashboard Chat",
|
||||
providerOverride: "openai",
|
||||
modelOverride: "gpt-test-a",
|
||||
parentSessionKey: "agent:main:main",
|
||||
});
|
||||
expect(rawStore[key]?.sessionId).toBe(created.payload?.sessionId);
|
||||
expect(rawStore[key]?.label).toBe("Dashboard Chat");
|
||||
expect(rawStore[key]?.providerOverride).toBe("openai");
|
||||
expect(rawStore[key]?.modelOverride).toBe("gpt-test-a");
|
||||
expect(rawStore[key]?.parentSessionKey).toBe("agent:main:main");
|
||||
expect(sessionFile).toBe(rawStore[key]?.sessionFile);
|
||||
|
||||
const transcriptPath = path.join(dir, `${created.payload?.sessionId}.jsonl`);
|
||||
const transcript = await fs.readFile(transcriptPath, "utf-8");
|
||||
const [headerLine] = transcript.trim().split(/\r?\n/, 1);
|
||||
expect(JSON.parse(headerLine) as { type?: string; id?: string }).toMatchObject({
|
||||
type: "session",
|
||||
id: created.payload?.sessionId,
|
||||
});
|
||||
const header = JSON.parse(headerLine) as { type?: string; id?: string };
|
||||
expect(header.type).toBe("session");
|
||||
expect(header.id).toBe(created.payload?.sessionId);
|
||||
});
|
||||
|
||||
test("sessions.create accepts an explicit key for persistent dashboard sessions", async () => {
|
||||
|
||||
Reference in New Issue
Block a user