mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-07 07:58:36 +00:00
@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/runtime deps: reuse enclosing versioned cache roots when bundled plugins resolve from nested staged paths, so plugin-runtime-deps no longer mints `openclaw-unknown-*` directories or loops on `ENOTEMPTY`. Fixes #72956. (#73205) Thanks @SymbolStar.
|
||||
- Agents/failover: classify CJK provider transport, quota, billing, auth, and overload error text so Chinese-language provider failures trigger fallback and user-facing transport copy instead of surfacing as unclassified raw errors. (#56242) Thanks @tomcatzh.
|
||||
- Agents/failover: seed non-claude-cli fallback prompts with Claude Code session context when a claude-cli attempt fails, so fallback models do not restart cold after billing or quota failover. (#72069) Thanks @stainlu.
|
||||
- Agents/CLI runner: transfer bundle-MCP tempDir cleanup from the per-turn runner finally to the Claude live-session lifecycle, so persistent Claude CLI sessions keep their `--mcp-config` directory until the live subprocess closes. Fixes #73244. Thanks @edwin-rivera-dev.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
|
||||
@@ -834,6 +834,60 @@ describe("runCliAgent spawn path", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("defers prepared backend cleanup to the Claude live session lifecycle", async () => {
|
||||
let stdoutListener: ((chunk: string) => void) | undefined;
|
||||
const stdin = {
|
||||
write: vi.fn((_data: string, cb?: (err?: Error | null) => void) => {
|
||||
stdoutListener?.(
|
||||
[
|
||||
JSON.stringify({ type: "system", subtype: "init", session_id: "live-session-cleanup" }),
|
||||
JSON.stringify({
|
||||
type: "result",
|
||||
session_id: "live-session-cleanup",
|
||||
result: "ok",
|
||||
}),
|
||||
].join("\n") + "\n",
|
||||
);
|
||||
cb?.();
|
||||
}),
|
||||
end: vi.fn(),
|
||||
};
|
||||
supervisorSpawnMock.mockImplementation(async (...args: unknown[]) => {
|
||||
const input = (args[0] ?? {}) as { onStdout?: (chunk: string) => void };
|
||||
stdoutListener = input.onStdout;
|
||||
return {
|
||||
runId: "live-cleanup-run",
|
||||
pid: 2346,
|
||||
startedAtMs: Date.now(),
|
||||
stdin,
|
||||
wait: vi.fn(() => new Promise(() => {})),
|
||||
cancel: vi.fn(),
|
||||
};
|
||||
});
|
||||
const preparedBackendCleanup = vi.fn(async () => {});
|
||||
const context = buildPreparedCliRunContext({
|
||||
provider: "claude-cli",
|
||||
model: "sonnet",
|
||||
runId: "run-live-cleanup",
|
||||
prompt: "first",
|
||||
backend: {
|
||||
args: ["-p", "--strict-mcp-config", "--mcp-config", "/tmp/mcp-cleanup.json"],
|
||||
liveSession: "claude-stdio",
|
||||
},
|
||||
mcpConfigHash: "cleanup-mcp-config",
|
||||
});
|
||||
context.preparedBackend.cleanup = preparedBackendCleanup;
|
||||
|
||||
const result = await executePreparedCliRun(context);
|
||||
|
||||
expect(result.text).toBe("ok");
|
||||
expect(context.preparedBackend.cleanup).toBeUndefined();
|
||||
expect(preparedBackendCleanup).not.toHaveBeenCalled();
|
||||
|
||||
resetClaudeLiveSessionsForTest();
|
||||
await vi.waitFor(() => expect(preparedBackendCleanup).toHaveBeenCalledOnce());
|
||||
});
|
||||
|
||||
it("accepts Claude live stream-json lines larger than 256 KiB", async () => {
|
||||
const largeText = "x".repeat(270 * 1024);
|
||||
let stdoutListener: ((chunk: string) => void) | undefined;
|
||||
|
||||
@@ -340,6 +340,8 @@ export async function executePreparedCliRun(
|
||||
throw new Error("Claude live session requires JSONL streaming parser");
|
||||
}
|
||||
claudeSkillsPluginCleanupOwned = true;
|
||||
const ownedPreparedBackendCleanup = context.preparedBackend.cleanup;
|
||||
context.preparedBackend.cleanup = undefined;
|
||||
const liveResult = await runClaudeLiveSessionTurn({
|
||||
context,
|
||||
args,
|
||||
@@ -364,7 +366,13 @@ export async function executePreparedCliRun(
|
||||
},
|
||||
});
|
||||
},
|
||||
cleanup: claudeSkillsPlugin.cleanup,
|
||||
cleanup: async () => {
|
||||
try {
|
||||
await claudeSkillsPlugin.cleanup();
|
||||
} finally {
|
||||
await ownedPreparedBackendCleanup?.();
|
||||
}
|
||||
},
|
||||
});
|
||||
const rawText = liveResult.output.text;
|
||||
return {
|
||||
|
||||
@@ -26,9 +26,9 @@ async function loadProviderRegistry() {
|
||||
vi.resetModules();
|
||||
return await import("./provider-registry.js");
|
||||
}
|
||||
|
||||
describe("video-generation provider registry", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
resolvePluginCapabilityProvidersMock.mockReset();
|
||||
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
|
||||
});
|
||||
@@ -44,8 +44,8 @@ describe("video-generation provider registry", () => {
|
||||
});
|
||||
|
||||
it("uses active plugin providers without loading from disk", async () => {
|
||||
const { getVideoGenerationProvider } = await loadProviderRegistry();
|
||||
resolvePluginCapabilityProvidersMock.mockReturnValue([createProvider({ id: "custom-video" })]);
|
||||
const { getVideoGenerationProvider } = await loadProviderRegistry();
|
||||
|
||||
const provider = getVideoGenerationProvider("custom-video");
|
||||
|
||||
@@ -57,12 +57,12 @@ describe("video-generation provider registry", () => {
|
||||
});
|
||||
|
||||
it("ignores prototype-like provider ids and aliases", async () => {
|
||||
const { getVideoGenerationProvider, listVideoGenerationProviders } =
|
||||
await loadProviderRegistry();
|
||||
resolvePluginCapabilityProvidersMock.mockReturnValue([
|
||||
createProvider({ id: "__proto__", aliases: ["constructor", "prototype"] }),
|
||||
createProvider({ id: "safe-video", aliases: ["safe-alias", "constructor"] }),
|
||||
]);
|
||||
const { getVideoGenerationProvider, listVideoGenerationProviders } =
|
||||
await loadProviderRegistry();
|
||||
|
||||
expect(listVideoGenerationProviders().map((provider) => provider.id)).toEqual(["safe-video"]);
|
||||
expect(getVideoGenerationProvider("__proto__")).toBeUndefined();
|
||||
|
||||
@@ -70,10 +70,6 @@ describe("package Telegram live Docker E2E", () => {
|
||||
expect(script).toContain('cp "$openclaw_package_dir/package.json" /app/package.json');
|
||||
expect(script).toContain('ln -sfnT /app/extensions "$openclaw_package_dir/extensions"');
|
||||
expect(script).toContain('"/app/node_modules/openclaw/package.json"');
|
||||
expect(script).toContain('pkg.exports["./plugin-sdk/qa-channel"]');
|
||||
expect(script).toContain('"./extensions/qa-channel/api.ts"');
|
||||
expect(script).toContain('pkg.exports["./plugin-sdk/qa-channel-protocol"]');
|
||||
expect(script).toContain('"./extensions/qa-channel/src/protocol.ts"');
|
||||
expect(script).toContain('pkg.exports["./plugin-sdk/gateway-runtime"]');
|
||||
expect(script).toContain('"./dist/plugin-sdk/gateway-runtime.js"');
|
||||
expect(gatewayRpcClient).toContain('from "openclaw/plugin-sdk/gateway-runtime"');
|
||||
|
||||
@@ -69,12 +69,7 @@ import { createUtilsVitestConfig } from "./vitest/vitest.utils.config.ts";
|
||||
import { createWizardVitestConfig } from "./vitest/vitest.wizard.config.ts";
|
||||
|
||||
const EXTENSIONS_CHANNEL_GLOB = ["extensions", "channel", "**"].join("/");
|
||||
const PRIVATE_PLUGIN_SDK_SUBPATHS = [
|
||||
"qa-channel",
|
||||
"qa-channel-protocol",
|
||||
"qa-lab",
|
||||
"qa-runtime",
|
||||
] as const;
|
||||
const PRIVATE_PLUGIN_SDK_SUBPATHS = ["qa-lab", "qa-runtime"] as const;
|
||||
|
||||
function bundledExcludePatternCouldMatchFile(pattern: string, file: string): boolean {
|
||||
if (pattern === file) {
|
||||
|
||||
Reference in New Issue
Block a user