mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
Merge remote-tracking branch 'origin/main' into fix/inline-slash-skills
This commit is contained in:
@@ -47,6 +47,7 @@ function prepare(root) {
|
||||
...packageJson.scripts,
|
||||
openclaw: "node openclaw.mjs",
|
||||
};
|
||||
delete packageJson.scripts.postinstall;
|
||||
const aiRuntimeSource = path.join(root, "node_modules", "@openclaw", "ai");
|
||||
const aiRuntimePackageJson = path.join(aiRuntimeSource, "package.json");
|
||||
if (!fs.existsSync(aiRuntimePackageJson)) {
|
||||
|
||||
@@ -140,7 +140,10 @@ export function normalizeClaudeCliStreamJsonRecord(
|
||||
return normalized ? { line: JSON.stringify(parsed), omittedRawChars } : undefined;
|
||||
}
|
||||
|
||||
function streamJsonOutputLimitErrorText(kind: "raw" | "line" | "lines", limit: number): string {
|
||||
export function streamJsonOutputLimitErrorText(
|
||||
kind: "raw" | "line" | "lines",
|
||||
limit: number,
|
||||
): string {
|
||||
if (kind === "line") {
|
||||
return `CLI JSONL line exceeded ${limit} characters; refusing to parse output.`;
|
||||
}
|
||||
|
||||
@@ -847,7 +847,7 @@ describe("Claude live turn output bounds and result projection", () => {
|
||||
});
|
||||
|
||||
await expect(startLiveTurn("run-live-oversized-line", false)).rejects.toThrow(
|
||||
"Claude CLI JSONL line exceeded output limit.",
|
||||
"CLI JSONL line exceeded 8388608 characters; refusing to parse output.",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -855,14 +855,17 @@ describe("Claude live turn output bounds and result projection", () => {
|
||||
{
|
||||
name: "a coalesced blank-frame flood",
|
||||
createChunk: () => "\n".repeat(20_001),
|
||||
expectedError: "CLI JSONL output exceeded 20000 lines; refusing to parse output.",
|
||||
},
|
||||
{
|
||||
name: "whitespace-only records exceeding the raw budget",
|
||||
createChunk: () => `${" ".repeat(4_300_000)}\n${" ".repeat(4_300_000)}\n`,
|
||||
expectedError: "CLI JSONL output exceeded 8388608 characters; refusing to parse output.",
|
||||
},
|
||||
{
|
||||
name: "valid JSON padded beyond the raw budget",
|
||||
createChunk: () => `${" ".repeat(4_300_000)}{}\n${" ".repeat(4_300_000)}{}\n`,
|
||||
expectedError: "CLI JSONL output exceeded 8388608 characters; refusing to parse output.",
|
||||
},
|
||||
{
|
||||
name: "internal formatting around compacted Claude media",
|
||||
@@ -886,14 +889,32 @@ describe("Claude live turn output bounds and result projection", () => {
|
||||
}).replace('"message":', `"message":${" ".repeat(4_300_000)}`);
|
||||
return `${line}\n${line}\n`;
|
||||
},
|
||||
expectedError: "CLI JSONL output exceeded 8388608 characters; refusing to parse output.",
|
||||
},
|
||||
])("rejects $name from the managed Claude live session", async ({ createChunk }) => {
|
||||
])("reports the exact limit for $name", async ({ createChunk, expectedError }) => {
|
||||
const live: ReturnType<typeof mockClaudeLiveRun> = mockClaudeLiveRun(supervisorSpawnMock, {
|
||||
onWrite: () => live.spawnInput.onStdout?.(createChunk()),
|
||||
});
|
||||
|
||||
await expect(startLiveTurn("run-live-output-budget", false)).rejects.toThrow(
|
||||
"Claude CLI turn output exceeded limit.",
|
||||
await expect(startLiveTurn("run-live-output-budget", false)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
it("reports backend JSONL parser failures without relabeling them as output limits", async () => {
|
||||
mockClaudeLiveRun(supervisorSpawnMock, {
|
||||
events: [{ type: "system", subtype: "init", session_id: "live-parser-error" }],
|
||||
});
|
||||
|
||||
await expectRejectsWithFields(
|
||||
startLiveTurn("run-live-parser-error", false, {
|
||||
parseJsonlEvent: () => {
|
||||
throw new Error("invalid custom event");
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "FailoverError",
|
||||
reason: "format",
|
||||
message: "CLI backend claude-cli JSONL parser failed: invalid custom event",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
createCliJsonlStreamingParser,
|
||||
frameBoundedCliJsonlChunk,
|
||||
normalizeClaudeCliStreamJsonRecord,
|
||||
streamJsonOutputLimitErrorText,
|
||||
} from "../cli-output-stream.js";
|
||||
import { parseCliOutput } from "../cli-output.js";
|
||||
import type { FailoverError } from "../failover-error.js";
|
||||
@@ -360,10 +361,11 @@ function applyBackgroundTasksChanged(
|
||||
|
||||
function pushTurnLine(host: ClaudeLiveTurnHost, turn: ClaudeLiveTurn, line: string): boolean {
|
||||
turn.streamingParser.push(`${line}\n`);
|
||||
if (!turn.streamingParser.getErrorText()) {
|
||||
const errorText = turn.streamingParser.getErrorText();
|
||||
if (!errorText) {
|
||||
return true;
|
||||
}
|
||||
host.close("abort", createClaudeOutputLimitError(host, "Claude CLI turn output exceeded limit."));
|
||||
host.close("abort", createClaudeOutputLimitError(host, errorText));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -492,7 +494,10 @@ export function acceptClaudeStdout(host: ClaudeLiveTurnHost, chunk: string): voi
|
||||
) {
|
||||
host.close(
|
||||
"abort",
|
||||
createClaudeOutputLimitError(host, "Claude CLI JSONL line exceeded output limit."),
|
||||
createClaudeOutputLimitError(
|
||||
host,
|
||||
streamJsonOutputLimitErrorText("line", maxPendingLineChars),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -231,7 +231,10 @@ function requireManifestRegistryLoadParams(index = 0): Record<string, unknown> {
|
||||
return call[0];
|
||||
}
|
||||
|
||||
function expectManifestRegistryLoad(index: number, config: OpenClawConfig | Record<string, never>) {
|
||||
function expectManifestRegistryLoad(
|
||||
index: number,
|
||||
config: OpenClawConfig | Record<string, never> | undefined,
|
||||
) {
|
||||
const params = requireManifestRegistryLoadParams(index);
|
||||
expect(params.config).toEqual(config);
|
||||
expect(params.env).toBe(process.env);
|
||||
@@ -1669,7 +1672,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
const providers = resolvePluginCapabilityProviders({ key: "mediaUnderstandingProviders" });
|
||||
|
||||
expectResolvedCapabilityProviderIds(providers, ["google"]);
|
||||
expectManifestRegistryLoad(0, {});
|
||||
expectManifestRegistryLoad(0, undefined);
|
||||
expectActiveRegistryLookup(["google"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,30 @@ function osc8Targets(raw: string) {
|
||||
}
|
||||
|
||||
describe("HyperlinkMarkdown", () => {
|
||||
it("does not reallocate prepared lines for an unchanged same-width redraw", () => {
|
||||
const markdown = new HyperlinkMarkdown(
|
||||
"مرحبا [docs](https://example.test/path)",
|
||||
0,
|
||||
0,
|
||||
markdownTheme,
|
||||
);
|
||||
|
||||
const first = markdown.render(80);
|
||||
expect(markdown.render(80)).toBe(first);
|
||||
|
||||
const resized = markdown.render(40);
|
||||
expect(resized).not.toBe(first);
|
||||
expect(markdown.render(40)).toBe(resized);
|
||||
|
||||
markdown.setText("updated");
|
||||
const updated = markdown.render(40);
|
||||
expect(updated).not.toBe(resized);
|
||||
expect(markdown.render(40)).toBe(updated);
|
||||
|
||||
markdown.invalidate();
|
||||
expect(markdown.render(40)).not.toBe(updated);
|
||||
});
|
||||
|
||||
it("moves dunder identifiers intact across fenced code wrap boundaries", () => {
|
||||
const markdown = new HyperlinkMarkdown(
|
||||
["```python", 'if __name__ == "__main__":', "```"].join("\n"),
|
||||
|
||||
@@ -24,6 +24,7 @@ function sanitizeMarkdownDisplayText(text: string): string {
|
||||
export class HyperlinkMarkdown implements Component {
|
||||
private inner: Markdown;
|
||||
private urls: string[];
|
||||
private cachedRender?: { width: number; lines: string[] };
|
||||
|
||||
constructor(
|
||||
text: string,
|
||||
@@ -39,16 +40,25 @@ export class HyperlinkMarkdown implements Component {
|
||||
}
|
||||
|
||||
render(width: number): string[] {
|
||||
return addOsc8Hyperlinks(this.inner.render(width), this.urls).map(isolateRtlRenderedLine);
|
||||
if (this.cachedRender?.width === width) {
|
||||
return this.cachedRender.lines;
|
||||
}
|
||||
const lines = addOsc8Hyperlinks(this.inner.render(width), this.urls).map(
|
||||
isolateRtlRenderedLine,
|
||||
);
|
||||
this.cachedRender = { width, lines };
|
||||
return lines;
|
||||
}
|
||||
|
||||
setText(text: string): void {
|
||||
const displayText = sanitizeMarkdownDisplayText(text);
|
||||
this.inner.setText(displayText);
|
||||
this.urls = extractUrls(displayText);
|
||||
this.cachedRender = undefined;
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.inner.invalidate();
|
||||
this.cachedRender = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ describe("package git fixture", () => {
|
||||
scripts: {
|
||||
build: "node build.mjs",
|
||||
openclaw: "node scripts/run-node.mjs",
|
||||
postinstall: "node scripts/postinstall-bundled-plugins.mjs",
|
||||
},
|
||||
},
|
||||
null,
|
||||
@@ -98,6 +99,7 @@ describe("package git fixture", () => {
|
||||
scripts: {
|
||||
lint: "node lint.mjs",
|
||||
openclaw: "node scripts/run-node.mjs",
|
||||
postinstall: "node scripts/postinstall-bundled-plugins.mjs",
|
||||
},
|
||||
},
|
||||
null,
|
||||
@@ -113,12 +115,9 @@ describe("package git fixture", () => {
|
||||
|
||||
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
|
||||
const packageJson = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
|
||||
expect(packageJson).toMatchObject({
|
||||
dependencies: { chalk: "5.6.2" },
|
||||
scripts: {
|
||||
lint: "node lint.mjs",
|
||||
openclaw: "node openclaw.mjs",
|
||||
},
|
||||
expect(packageJson.scripts).toEqual({
|
||||
lint: "node lint.mjs",
|
||||
openclaw: "node openclaw.mjs",
|
||||
});
|
||||
expect(packageJson.dependencies).not.toHaveProperty("@openclaw/ai");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user