mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
test: remove low-value implementation assertions (#121085)
* test: remove low-value implementation assertions * test: refresh native i18n inventory
This commit is contained in:
committed by
GitHub
parent
e750dab6d1
commit
0303af17f3
@@ -32561,14 +32561,6 @@
|
||||
"surface": "apple",
|
||||
"id": "native.apple.e7d1ee5662c1d646"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 1020,
|
||||
"path": "apps/macos/Sources/OpenClaw/DebugSettings.swift",
|
||||
"source": "Test",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.0397fa643df26663"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 74,
|
||||
|
||||
@@ -973,53 +973,4 @@ struct DebugSettings_Previews: PreviewProvider {
|
||||
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
extension DebugSettings {
|
||||
static func exerciseForTesting() async {
|
||||
let view = DebugSettings(state: .preview)
|
||||
view.gatewayRootInput = "/tmp/openclaw"
|
||||
view.sessionStorePath = "/tmp/sessions.json"
|
||||
view.sessionStoreSaveError = "Save failed"
|
||||
view.debugSendInFlight = true
|
||||
view.debugSendStatus = "Sent"
|
||||
view.debugSendError = "Failed"
|
||||
view.portCheckInFlight = true
|
||||
view.portReports = [
|
||||
DebugActions.PortReport(
|
||||
port: GatewayEnvironment.gatewayPort(),
|
||||
expected: "Gateway websocket (node/tsx)",
|
||||
status: .missing("Missing"),
|
||||
listeners: []),
|
||||
]
|
||||
view.portKillStatus = "Killed"
|
||||
view.pendingKill = DebugActions.PortListener(
|
||||
pid: 1,
|
||||
command: "node",
|
||||
fullCommand: "node",
|
||||
user: nil,
|
||||
expected: true)
|
||||
view.canvasSessionKey = "main"
|
||||
view.canvasStatus = "Canvas ok"
|
||||
view.canvasError = "Canvas error"
|
||||
view.canvasEvalJS = "document.title"
|
||||
view.canvasEvalResult = "Canvas"
|
||||
view.canvasSnapshotPath = "/tmp/snapshot.png"
|
||||
|
||||
_ = view.body
|
||||
_ = view.header
|
||||
_ = view.overviewSection
|
||||
_ = view.appInfoSection
|
||||
_ = view.gatewaySection
|
||||
_ = view.logsSection
|
||||
_ = view.portsSection
|
||||
_ = view.pathsSection
|
||||
_ = view.quickActionsSection
|
||||
_ = view.canvasSection
|
||||
_ = view.experimentsSection
|
||||
_ = view.gridLabel("Test")
|
||||
|
||||
view.loadSessionStorePath()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -937,36 +937,4 @@ struct VoiceWakeSettings_Previews: PreviewProvider {
|
||||
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
extension VoiceWakeSettings {
|
||||
static func exerciseForTesting() {
|
||||
let state = AppState(preview: true)
|
||||
state.swabbleEnabled = true
|
||||
state.voicePushToTalkEnabled = true
|
||||
state.swabbleTriggerWords = ["Claude", "Hey"]
|
||||
|
||||
let view = VoiceWakeSettings(state: state, isActive: true)
|
||||
view.availableMics = [AudioInputDevice(uid: "mic-1", name: "Built-in")]
|
||||
view.availableLocales = [Locale(identifier: "en_US")]
|
||||
view.meterLevel = 0.42
|
||||
view.meterError = "No input"
|
||||
view.testState = .detected("ok")
|
||||
view.isTesting = true
|
||||
view.triggerEntries = [TriggerEntry(id: UUID(), value: "Claude")]
|
||||
|
||||
_ = view.body
|
||||
_ = view.localePicker
|
||||
_ = view.micPicker
|
||||
_ = view.levelMeter
|
||||
_ = view.triggerTable
|
||||
_ = view.chimeSection
|
||||
_ = view.unsupportedVoiceWakePanel
|
||||
|
||||
view.addWord()
|
||||
if let entryId = view.triggerEntries.first?.id {
|
||||
view.removeWord(id: entryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -93,12 +93,4 @@ struct LowCoverageViewSmokeTests {
|
||||
DockIconManager.shared.updateDockVisibility()
|
||||
DockIconManager.shared.temporarilyShowDock()
|
||||
}
|
||||
|
||||
@Test func `voice wake settings exercises helpers`() {
|
||||
VoiceWakeSettings.exerciseForTesting()
|
||||
}
|
||||
|
||||
@Test func `debug settings exercises helpers`() async {
|
||||
await DebugSettings.exerciseForTesting()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Chutes tests cover models plugin behavior.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CHUTES_DEFAULT_MODEL_ID } from "./api.js";
|
||||
@@ -98,22 +97,6 @@ describe("chutes-models", () => {
|
||||
clearLiveCatalogCacheForTests();
|
||||
});
|
||||
|
||||
it("builds static definitions with required fields", () => {
|
||||
const entry = expectDefined(CHUTES_MODEL_CATALOG[0], "first Chutes catalog model");
|
||||
const def = entry;
|
||||
expect(def.id).toBe(entry.id);
|
||||
expect(def.name).toBe(entry.name);
|
||||
expect(def.reasoning).toBe(entry.reasoning);
|
||||
expect(def.input).toEqual(entry.input);
|
||||
expect(def.cost).toEqual(entry.cost);
|
||||
expect(def.contextWindow).toBe(entry.contextWindow);
|
||||
expect(def.maxTokens).toBe(entry.maxTokens);
|
||||
if (!def.compat) {
|
||||
throw new Error("expected Chutes model compat");
|
||||
}
|
||||
expect(def.compat.supportsUsageInStreaming).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps image-capable fallback models in the runtime catalog", () => {
|
||||
const visionModelIds = ["moonshotai/Kimi-K2.6-TEE", "Qwen/Qwen3.6-27B-TEE"];
|
||||
for (const id of visionModelIds) {
|
||||
@@ -131,6 +114,9 @@ describe("chutes-models", () => {
|
||||
const runtimeIds = CHUTES_MODEL_CATALOG.map((model) => model.id);
|
||||
expect(manifestIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
|
||||
expect(runtimeIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
|
||||
expect(
|
||||
CHUTES_MODEL_CATALOG.every((model) => model.compat?.supportsUsageInStreaming === false),
|
||||
).toBe(true);
|
||||
expect(CHUTES_DEFAULT_MODEL_ID).toBe(manifest.modelCatalog.providers.chutes.defaultModel);
|
||||
expect(manifest.modelCatalog.providers.chutes.defaultModel).toBe("zai-org/GLM-5.2-TEE");
|
||||
expect(
|
||||
|
||||
@@ -922,6 +922,54 @@ describe("comfy image-generation provider", () => {
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("bounds oversized local workflow submit responses and releases the request", async () => {
|
||||
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
|
||||
const chunk = new Uint8Array(1024 * 1024);
|
||||
const totalBytes = 32 * chunk.length;
|
||||
let bytesPulled = 0;
|
||||
let canceled = false;
|
||||
const release = vi.fn(async () => {});
|
||||
fetchWithSsrFGuardMock.mockResolvedValueOnce({
|
||||
response: new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (bytesPulled >= totalBytes) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
bytesPulled += chunk.length;
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
release,
|
||||
});
|
||||
|
||||
const provider = buildComfyImageGenerationProvider();
|
||||
await expect(
|
||||
provider.generateImage({
|
||||
provider: "comfy",
|
||||
model: "workflow",
|
||||
prompt: "draw a lobster",
|
||||
cfg: buildComfyConfig({
|
||||
workflow: {
|
||||
"6": { inputs: { text: "" } },
|
||||
"9": { inputs: {} },
|
||||
},
|
||||
promptNodeId: "6",
|
||||
outputNodeId: "9",
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrow("Comfy workflow submit failed: JSON response exceeds 16777216 bytes");
|
||||
expect(canceled).toBe(true);
|
||||
expect(bytesPulled).toBeLessThan(totalBytes);
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("uploads reference images for local edit workflows", async () => {
|
||||
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
|
||||
fetchWithSsrFGuardMock
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
// Comfy tests cover workflow-runtime bounded-read delegation.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { setComfyFetchGuardForTesting } from "./test-support.js";
|
||||
import { readJsonResponseForTest } from "./workflow-runtime.js";
|
||||
|
||||
describe("readJsonResponse bounded read (readProviderJsonResponse delegation)", () => {
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setComfyFetchGuardForTesting(null);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("cancels oversized JSON body via the 16 MiB provider cap", async () => {
|
||||
const ONE_MIB = 1024 * 1024;
|
||||
const TOTAL_CHUNKS = 32;
|
||||
const chunk = new Uint8Array(ONE_MIB);
|
||||
|
||||
let bytesPulled = 0;
|
||||
let canceled = false;
|
||||
const oversizedJson = new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (bytesPulled >= TOTAL_CHUNKS * ONE_MIB) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
bytesPulled += chunk.length;
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
|
||||
const release = vi.fn(async () => {});
|
||||
fetchMock.mockResolvedValueOnce({ response: oversizedJson, release });
|
||||
setComfyFetchGuardForTesting(fetchMock);
|
||||
|
||||
await expect(
|
||||
readJsonResponseForTest({
|
||||
url: "http://127.0.0.1:9999/test",
|
||||
init: { method: "GET" },
|
||||
timeoutMs: 10_000,
|
||||
auditContext: "comfy-test",
|
||||
errorPrefix: "Comfy test failed",
|
||||
}),
|
||||
).rejects.toThrow(/JSON response exceeds 16777216 bytes/);
|
||||
|
||||
expect(canceled).toBe(true);
|
||||
expect(bytesPulled).toBeLessThan(TOTAL_CHUNKS * ONE_MIB);
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("rejects oversized body with correct error prefix", async () => {
|
||||
const ONE_MIB = 1024 * 1024;
|
||||
const chunk = new Uint8Array(ONE_MIB);
|
||||
|
||||
let bytesPulled = 0;
|
||||
let canceled = false;
|
||||
const oversizedJson = new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (bytesPulled >= 32 * ONE_MIB) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
bytesPulled += chunk.length;
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
|
||||
const release = vi.fn(async () => {});
|
||||
fetchMock.mockResolvedValueOnce({ response: oversizedJson, release });
|
||||
setComfyFetchGuardForTesting(fetchMock);
|
||||
|
||||
await expect(
|
||||
readJsonResponseForTest({
|
||||
url: "http://127.0.0.1:9999/test",
|
||||
init: { method: "GET" },
|
||||
timeoutMs: 10_000,
|
||||
auditContext: "comfy-test",
|
||||
errorPrefix: "Comfy test failed",
|
||||
}),
|
||||
).rejects.toThrow(/^Comfy test failed: JSON response exceeds 16777216 bytes/);
|
||||
|
||||
expect(canceled).toBe(true);
|
||||
expect(bytesPulled).toBeLessThan(32 * ONE_MIB);
|
||||
});
|
||||
|
||||
it("parses small valid JSON body (negative control)", async () => {
|
||||
const smallBody = { status: "ok" };
|
||||
|
||||
const release = vi.fn(async () => {});
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
response: new Response(JSON.stringify(smallBody), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
release,
|
||||
});
|
||||
setComfyFetchGuardForTesting(fetchMock);
|
||||
|
||||
const result = await readJsonResponseForTest<{ status: string }>({
|
||||
url: "http://127.0.0.1:9999/test",
|
||||
init: { method: "GET" },
|
||||
timeoutMs: 10_000,
|
||||
auditContext: "comfy-test",
|
||||
errorPrefix: "Comfy test failed",
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("parses valid JSON with expected comfy response shape (happy path)", async () => {
|
||||
const comfyResponse = { prompt_id: "abc-123" };
|
||||
|
||||
const release = vi.fn(async () => {});
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
response: new Response(JSON.stringify(comfyResponse), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
release,
|
||||
});
|
||||
setComfyFetchGuardForTesting(fetchMock);
|
||||
|
||||
const result = await readJsonResponseForTest<{ prompt_id: string }>({
|
||||
url: "http://127.0.0.1:9999/test",
|
||||
init: { method: "GET" },
|
||||
timeoutMs: 10_000,
|
||||
auditContext: "comfy-test",
|
||||
errorPrefix: "Comfy test failed",
|
||||
});
|
||||
|
||||
expect(result.prompt_id).toBe("abc-123");
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("propagates HTTP error status before reading body", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
fetchMock.mockResolvedValueOnce({
|
||||
response: new Response(null, { status: 500, statusText: "Internal Server Error" }),
|
||||
release,
|
||||
});
|
||||
setComfyFetchGuardForTesting(fetchMock);
|
||||
|
||||
await expect(
|
||||
readJsonResponseForTest({
|
||||
url: "http://127.0.0.1:9999/test",
|
||||
init: { method: "GET" },
|
||||
timeoutMs: 10_000,
|
||||
auditContext: "comfy-test",
|
||||
errorPrefix: "Comfy test failed",
|
||||
}),
|
||||
).rejects.toThrow(/Comfy test failed/);
|
||||
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
@@ -347,9 +347,6 @@ async function readJsonResponse<T>(params: {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal Test-only export. */
|
||||
export const readJsonResponseForTest = readJsonResponse;
|
||||
|
||||
function resolveFileExtension(params: { fileName?: string; mimeType?: string }): string {
|
||||
const extension = extensionForMime(params.mimeType);
|
||||
if (extension) {
|
||||
|
||||
@@ -16,9 +16,9 @@ type CuaDriverSdk = Pick<
|
||||
|
||||
export type CuaToolResult = import("@trycua/cua-driver").ToolResult;
|
||||
|
||||
// These numeric values are part of the pinned 0.14.1 SDK contract and are also
|
||||
// frozen in driver-contract-fixtures. Keeping them local avoids loading the
|
||||
// native library while OpenClaw is only registering the bundled plugin.
|
||||
// These numeric values are part of the pinned 0.14.1 SDK contract. Keeping
|
||||
// them local avoids loading the native library while OpenClaw is only
|
||||
// registering the bundled plugin.
|
||||
export const ClickButton = {
|
||||
Left: 0 as DriverClickButton,
|
||||
Right: 1 as DriverClickButton,
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
/**
|
||||
* Frozen CUA Driver 0.14.1 desktop contract fixtures used by the CUA fulfiller tests.
|
||||
*
|
||||
* Evidence: cua-driver-rs-v0.14.1 (41ae29b44b49b68c6e01c934fffbbe74d22e26fb)
|
||||
* and @trycua/cua-driver@0.14.1. The SDK declarations name these methods with
|
||||
* camelCase and encode DesktopScope.Desktop as 0; MCP uses the snake_case tool
|
||||
* names and the "desktop" scope string below.
|
||||
*/
|
||||
export const CUA_DRIVER_0141_CONTRACT = {
|
||||
version: "0.14.1",
|
||||
releaseTag: "cua-driver-rs-v0.14.1",
|
||||
releaseCommit: "41ae29b44b49b68c6e01c934fffbbe74d22e26fb",
|
||||
npmIntegrity:
|
||||
"sha512-/o16k+vcTbdqwmvQqgFCKzrYksSQHz282qO8RkpD67GQoY4Vp3pyDndQexRJ8AbzNQpUt1bIXSAAaFBaMad6rg==",
|
||||
serverName: "cua-driver",
|
||||
capabilityVersion: "1",
|
||||
schemaVersion: "1",
|
||||
} as const;
|
||||
|
||||
export const CUA_DRIVER_0141_SDK_DESKTOP_SCOPE = 0 as const;
|
||||
|
||||
export const CUA_DRIVER_0141_SDK_FIXTURES = {
|
||||
getDesktopState: {},
|
||||
getScreenSize: {},
|
||||
getCursorPosition: {},
|
||||
click: {
|
||||
x: 960,
|
||||
y: 540,
|
||||
scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE,
|
||||
button: 0,
|
||||
count: 1,
|
||||
},
|
||||
drag: {
|
||||
fromX: 100,
|
||||
fromY: 200,
|
||||
toX: 960,
|
||||
toY: 540,
|
||||
scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE,
|
||||
durationMs: 500n,
|
||||
},
|
||||
moveCursor: { x: 960, y: 540, scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE },
|
||||
scroll: {
|
||||
x: 960,
|
||||
y: 540,
|
||||
direction: 1,
|
||||
scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE,
|
||||
by: 0,
|
||||
amount: 3n,
|
||||
},
|
||||
typeText: { text: "hello", scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE },
|
||||
pressKey: {
|
||||
key: "enter",
|
||||
scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE,
|
||||
modifiers: ["shift"],
|
||||
},
|
||||
hotkey: { keys: ["ctrl", "c"], scope: CUA_DRIVER_0141_SDK_DESKTOP_SCOPE },
|
||||
} as const;
|
||||
|
||||
export const CUA_DRIVER_0141_MCP_FIXTURES = {
|
||||
serverInfo: {
|
||||
name: CUA_DRIVER_0141_CONTRACT.serverName,
|
||||
version: CUA_DRIVER_0141_CONTRACT.version,
|
||||
},
|
||||
toolsList: {
|
||||
capability_version: CUA_DRIVER_0141_CONTRACT.capabilityVersion,
|
||||
schema_version: CUA_DRIVER_0141_CONTRACT.schemaVersion,
|
||||
},
|
||||
desktopState: {
|
||||
platform: "linux",
|
||||
display: "primary",
|
||||
screenshot_width: 3840,
|
||||
screenshot_height: 2160,
|
||||
screen_width: 3840,
|
||||
screen_height: 2160,
|
||||
scale_factor: 1,
|
||||
screenshot_mime_type: "image/png",
|
||||
},
|
||||
screenSize: { width: 3840, height: 2160, scale_factor: 1 },
|
||||
} as const;
|
||||
|
||||
export const CUA_DRIVER_0141_FAILURE_FIXTURES = {
|
||||
invalidArguments: {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: "click: invalid arguments: missing field `x`" }],
|
||||
structuredContent: { code: "invalid_arguments", tool: "click" },
|
||||
},
|
||||
refusal: {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: "desktop input is unavailable" }],
|
||||
structuredContent: { code: "desktop_unavailable" },
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const CUA_DRIVER_0141_GENERATION_FIXTURE = {
|
||||
initial: "connection-e8dcf30c",
|
||||
reconnected: "connection-7857c486",
|
||||
} as const;
|
||||
|
||||
export const CUA_DRIVER_0141_DESKTOP_OPERATIONS = [
|
||||
"get_desktop_state",
|
||||
"get_screen_size",
|
||||
"get_cursor_position",
|
||||
"click",
|
||||
"drag",
|
||||
"move_cursor",
|
||||
"scroll",
|
||||
"type_text",
|
||||
"press_key",
|
||||
"hotkey",
|
||||
] as const;
|
||||
|
||||
type CuaDriver0141DesktopOperation = (typeof CUA_DRIVER_0141_DESKTOP_OPERATIONS)[number];
|
||||
type CuaDriver0141SdkMethod = keyof typeof CUA_DRIVER_0141_SDK_FIXTURES;
|
||||
|
||||
export const CUA_DRIVER_0141_COMPUTER_ACT_PARITY = [
|
||||
{
|
||||
operation: "get_desktop_state",
|
||||
sdkMethod: "getDesktopState",
|
||||
disposition: "screen.snapshot",
|
||||
computerActions: ["screenshot"],
|
||||
},
|
||||
{
|
||||
operation: "get_screen_size",
|
||||
sdkMethod: "getScreenSize",
|
||||
disposition: "frame verification",
|
||||
computerActions: [],
|
||||
},
|
||||
{
|
||||
operation: "get_cursor_position",
|
||||
sdkMethod: "getCursorPosition",
|
||||
disposition: "not projected",
|
||||
computerActions: [],
|
||||
},
|
||||
{
|
||||
operation: "click",
|
||||
sdkMethod: "click",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["left_click", "right_click", "middle_click", "double_click", "triple_click"],
|
||||
},
|
||||
{
|
||||
operation: "drag",
|
||||
sdkMethod: "drag",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["left_click_drag"],
|
||||
},
|
||||
{
|
||||
operation: "move_cursor",
|
||||
sdkMethod: "moveCursor",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["mouse_move"],
|
||||
},
|
||||
{
|
||||
operation: "scroll",
|
||||
sdkMethod: "scroll",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["scroll"],
|
||||
},
|
||||
{
|
||||
operation: "type_text",
|
||||
sdkMethod: "typeText",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["type"],
|
||||
},
|
||||
{
|
||||
operation: "press_key",
|
||||
sdkMethod: "pressKey",
|
||||
disposition: "computer.act",
|
||||
computerActions: ["key"],
|
||||
},
|
||||
{
|
||||
operation: "hotkey",
|
||||
sdkMethod: "hotkey",
|
||||
disposition: "not projected",
|
||||
computerActions: [],
|
||||
},
|
||||
] as const satisfies readonly {
|
||||
operation: CuaDriver0141DesktopOperation;
|
||||
sdkMethod: CuaDriver0141SdkMethod;
|
||||
disposition: "screen.snapshot" | "frame verification" | "computer.act" | "not projected";
|
||||
computerActions: readonly string[];
|
||||
}[];
|
||||
|
||||
export const COMPUTER_ACT_ACTION_FIXTURES = [
|
||||
"screenshot",
|
||||
"left_click",
|
||||
"right_click",
|
||||
"middle_click",
|
||||
"double_click",
|
||||
"triple_click",
|
||||
"mouse_move",
|
||||
"left_click_drag",
|
||||
"left_mouse_down",
|
||||
"left_mouse_up",
|
||||
"scroll",
|
||||
"type",
|
||||
"key",
|
||||
"hold_key",
|
||||
"wait",
|
||||
] as const;
|
||||
|
||||
export const CUA_DRIVER_0141_UNSUPPORTED_COMPUTER_ACT_ACTIONS = [
|
||||
"hold_key",
|
||||
"left_mouse_down",
|
||||
"left_mouse_up",
|
||||
] as const;
|
||||
|
||||
export const CUA_DRIVER_0141_CORE_LOCAL_COMPUTER_ACT_ACTIONS = ["wait"] as const;
|
||||
@@ -1,76 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
COMPUTER_ACT_ACTION_FIXTURES,
|
||||
CUA_DRIVER_0141_COMPUTER_ACT_PARITY,
|
||||
CUA_DRIVER_0141_CONTRACT,
|
||||
CUA_DRIVER_0141_CORE_LOCAL_COMPUTER_ACT_ACTIONS,
|
||||
CUA_DRIVER_0141_DESKTOP_OPERATIONS,
|
||||
CUA_DRIVER_0141_FAILURE_FIXTURES,
|
||||
CUA_DRIVER_0141_GENERATION_FIXTURE,
|
||||
CUA_DRIVER_0141_MCP_FIXTURES,
|
||||
CUA_DRIVER_0141_SDK_DESKTOP_SCOPE,
|
||||
CUA_DRIVER_0141_SDK_FIXTURES,
|
||||
CUA_DRIVER_0141_UNSUPPORTED_COMPUTER_ACT_ACTIONS,
|
||||
} from "./driver-contract-fixtures.test-fixtures.js";
|
||||
|
||||
describe("cua-driver 0.14.1 computer.act parity fixtures", () => {
|
||||
it("pins the released CUA driver identity and SDK desktop-scope representation", () => {
|
||||
expect(CUA_DRIVER_0141_CONTRACT).toMatchObject({
|
||||
version: "0.14.1",
|
||||
releaseTag: "cua-driver-rs-v0.14.1",
|
||||
releaseCommit: "41ae29b44b49b68c6e01c934fffbbe74d22e26fb",
|
||||
serverName: "cua-driver",
|
||||
capabilityVersion: "1",
|
||||
schemaVersion: "1",
|
||||
});
|
||||
expect(CUA_DRIVER_0141_SDK_FIXTURES.click.scope).toBe(CUA_DRIVER_0141_SDK_DESKTOP_SCOPE);
|
||||
expect(CUA_DRIVER_0141_SDK_FIXTURES.drag.durationMs).toBe(500n);
|
||||
expect(Object.keys(CUA_DRIVER_0141_SDK_FIXTURES)).toEqual(
|
||||
CUA_DRIVER_0141_COMPUTER_ACT_PARITY.map(({ sdkMethod }) => sdkMethod),
|
||||
);
|
||||
expect(CUA_DRIVER_0141_MCP_FIXTURES.desktopState.screenshot_mime_type).toBe("image/png");
|
||||
});
|
||||
|
||||
it("freezes driver failures and reconnect generations for later runtime migration", () => {
|
||||
expect(CUA_DRIVER_0141_FAILURE_FIXTURES.invalidArguments).toMatchObject({
|
||||
isError: true,
|
||||
structuredContent: { code: "invalid_arguments", tool: "click" },
|
||||
});
|
||||
expect(CUA_DRIVER_0141_FAILURE_FIXTURES.refusal).toMatchObject({
|
||||
isError: true,
|
||||
structuredContent: { code: "desktop_unavailable" },
|
||||
});
|
||||
expect(typeof CUA_DRIVER_0141_GENERATION_FIXTURE.initial).toBe("string");
|
||||
expect(CUA_DRIVER_0141_GENERATION_FIXTURE.reconnected).not.toBe(
|
||||
CUA_DRIVER_0141_GENERATION_FIXTURE.initial,
|
||||
);
|
||||
});
|
||||
|
||||
it("classifies every frozen desktop operation and computer action exactly once", () => {
|
||||
const operations = CUA_DRIVER_0141_COMPUTER_ACT_PARITY.map(({ operation }) => operation);
|
||||
expect(new Set(operations).size).toBe(operations.length);
|
||||
expect(new Set(operations)).toEqual(new Set(CUA_DRIVER_0141_DESKTOP_OPERATIONS));
|
||||
|
||||
const classifiedActions = [
|
||||
...CUA_DRIVER_0141_COMPUTER_ACT_PARITY.flatMap(({ computerActions }) => computerActions),
|
||||
...CUA_DRIVER_0141_UNSUPPORTED_COMPUTER_ACT_ACTIONS,
|
||||
...CUA_DRIVER_0141_CORE_LOCAL_COMPUTER_ACT_ACTIONS,
|
||||
];
|
||||
expect(classifiedActions).toHaveLength(COMPUTER_ACT_ACTION_FIXTURES.length);
|
||||
expect(new Set(classifiedActions).size).toBe(COMPUTER_ACT_ACTION_FIXTURES.length);
|
||||
expect(new Set(classifiedActions)).toEqual(new Set(COMPUTER_ACT_ACTION_FIXTURES));
|
||||
});
|
||||
|
||||
it("keeps the existing model-facing projection narrow", () => {
|
||||
expect(
|
||||
CUA_DRIVER_0141_COMPUTER_ACT_PARITY.filter(
|
||||
({ disposition }) => disposition === "computer.act",
|
||||
).map(({ operation }) => operation),
|
||||
).toEqual(["click", "drag", "move_cursor", "scroll", "type_text", "press_key"]);
|
||||
expect(
|
||||
CUA_DRIVER_0141_COMPUTER_ACT_PARITY.filter(
|
||||
({ disposition }) => disposition === "not projected",
|
||||
).map(({ operation }) => operation),
|
||||
).toEqual(["get_cursor_position", "hotkey"]);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// Diffs tests cover manifest plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
type DiffsPackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
describe("diffs package manifest", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||
) as DiffsPackageManifest;
|
||||
|
||||
expect(packageJson.dependencies).toHaveProperty("@pierre/diffs");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
// Huggingface API module exposes the plugin public contract.
|
||||
export {
|
||||
buildHuggingfaceModelDefinition,
|
||||
discoverHuggingfaceModels,
|
||||
HUGGINGFACE_BASE_URL,
|
||||
HUGGINGFACE_MODEL_CATALOG,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildHuggingfaceModelDefinition,
|
||||
discoverHuggingfaceModels,
|
||||
HUGGINGFACE_MODEL_CATALOG,
|
||||
isHuggingfacePolicyLocked,
|
||||
@@ -42,18 +41,6 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("huggingface models", () => {
|
||||
it("buildHuggingfaceModelDefinition returns config with required fields", () => {
|
||||
const entry = expectDefined(HUGGINGFACE_MODEL_CATALOG[0], "first Hugging Face catalog model");
|
||||
const def = buildHuggingfaceModelDefinition(entry);
|
||||
expect(def.id).toBe(entry.id);
|
||||
expect(def.name).toBe(entry.name);
|
||||
expect(def.reasoning).toBe(entry.reasoning);
|
||||
expect(def.input).toEqual(entry.input);
|
||||
expect(def.cost).toEqual(entry.cost);
|
||||
expect(def.contextWindow).toBe(entry.contextWindow);
|
||||
expect(def.maxTokens).toBe(entry.maxTokens);
|
||||
});
|
||||
|
||||
it("does not advertise the retired Llama 3.3 Turbo route", () => {
|
||||
expect(HUGGINGFACE_MODEL_CATALOG.map((model) => model.id)).not.toContain(
|
||||
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
||||
|
||||
@@ -77,20 +77,6 @@ export function isHuggingfacePolicyLocked(modelRef: string): boolean {
|
||||
return HUGGINGFACE_POLICY_SUFFIXES.some((suffix) => ref.endsWith(`:${suffix}`) || ref === suffix);
|
||||
}
|
||||
|
||||
export function buildHuggingfaceModelDefinition(
|
||||
model: (typeof HUGGINGFACE_MODEL_CATALOG)[number],
|
||||
): ModelDefinitionConfig {
|
||||
return {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
reasoning: model.reasoning,
|
||||
input: model.input,
|
||||
cost: model.cost,
|
||||
contextWindow: model.contextWindow,
|
||||
maxTokens: model.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
function isReasoningModelHeuristic(modelId: string): boolean {
|
||||
const lower = normalizeLowercaseStringOrEmpty(modelId);
|
||||
return (
|
||||
@@ -147,7 +133,7 @@ function projectHuggingfaceModels(rows: readonly unknown[]): ModelDefinitionConf
|
||||
|
||||
const catalogEntry = catalogById.get(id);
|
||||
if (catalogEntry) {
|
||||
models.push(buildHuggingfaceModelDefinition(catalogEntry));
|
||||
models.push(Object.assign({}, catalogEntry));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -176,12 +162,12 @@ export async function discoverHuggingfaceModels(
|
||||
timeoutMs = HUGGINGFACE_DISCOVERY_TIMEOUT_MS,
|
||||
): Promise<ModelDefinitionConfig[]> {
|
||||
if (isHuggingfaceModelDiscoveryTestEnvironment()) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
return HUGGINGFACE_MODEL_CATALOG.map((model) => Object.assign({}, model));
|
||||
}
|
||||
|
||||
const trimmedKey = apiKey?.trim();
|
||||
if (!trimmedKey) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
return HUGGINGFACE_MODEL_CATALOG.map((model) => Object.assign({}, model));
|
||||
}
|
||||
|
||||
const requestTimeoutMs = resolveTimerTimeoutMs(timeoutMs, HUGGINGFACE_DISCOVERY_TIMEOUT_MS);
|
||||
@@ -189,7 +175,7 @@ export async function discoverHuggingfaceModels(
|
||||
providerId: "huggingface",
|
||||
endpoint: `${HUGGINGFACE_BASE_URL}/models`,
|
||||
providerConfig: { baseUrl: HUGGINGFACE_BASE_URL, api: "openai-completions" },
|
||||
models: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition),
|
||||
models: HUGGINGFACE_MODEL_CATALOG.map((model) => Object.assign({}, model)),
|
||||
discoveryApiKey: trimmedKey,
|
||||
signal: AbortSignal.timeout(requestTimeoutMs),
|
||||
timeoutMs: requestTimeoutMs,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// Huggingface setup module handles plugin onboarding behavior.
|
||||
import { createModelCatalogPresetAppliers } from "openclaw/plugin-sdk/provider-onboard";
|
||||
import {
|
||||
buildHuggingfaceModelDefinition,
|
||||
HUGGINGFACE_BASE_URL,
|
||||
HUGGINGFACE_MODEL_CATALOG,
|
||||
} from "./models.js";
|
||||
import { HUGGINGFACE_BASE_URL, HUGGINGFACE_MODEL_CATALOG } from "./models.js";
|
||||
|
||||
export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1";
|
||||
|
||||
@@ -14,7 +10,7 @@ export const { applyConfig: applyHuggingfaceConfig } = createModelCatalogPresetA
|
||||
providerId: "huggingface",
|
||||
api: "openai-completions",
|
||||
baseUrl: HUGGINGFACE_BASE_URL,
|
||||
catalogModels: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition),
|
||||
catalogModels: HUGGINGFACE_MODEL_CATALOG.map((model) => Object.assign({}, model)),
|
||||
aliases: [{ modelRef: HUGGINGFACE_DEFAULT_MODEL_REF, alias: "Hugging Face" }],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Huggingface provider module implements model/runtime integration.
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
import {
|
||||
buildHuggingfaceModelDefinition,
|
||||
discoverHuggingfaceModels,
|
||||
HUGGINGFACE_BASE_URL,
|
||||
HUGGINGFACE_MODEL_CATALOG,
|
||||
@@ -14,7 +13,7 @@ export async function buildHuggingfaceProvider(
|
||||
const models =
|
||||
resolvedSecret !== ""
|
||||
? await discoverHuggingfaceModels(resolvedSecret)
|
||||
: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
: HUGGINGFACE_MODEL_CATALOG.map((model) => Object.assign({}, model));
|
||||
return {
|
||||
baseUrl: HUGGINGFACE_BASE_URL,
|
||||
api: "openai-completions",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Telegram tests cover plain-text chunk-splitting behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { splitTelegramPlainTextChunksForTests } from "./send.js";
|
||||
import { splitTelegramPlainTextChunks } from "./rich-plain-fallback.js";
|
||||
|
||||
function containsLoneSurrogate(text: string): boolean {
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
@@ -24,7 +24,7 @@ describe("splitTelegramPlainTextChunks", () => {
|
||||
it("does not split an astral char across the chunk boundary", () => {
|
||||
// Emoji surrogate pair straddles index 10 (limit): high at 9, low at 10.
|
||||
const input = `${"A".repeat(9)}😀${"B".repeat(20)}`;
|
||||
const chunks = splitTelegramPlainTextChunksForTests(input, 10);
|
||||
const chunks = splitTelegramPlainTextChunks(input, 10);
|
||||
expect(chunks.length).toBeGreaterThan(1);
|
||||
expect(chunks.join("")).toBe(input);
|
||||
for (const chunk of chunks) {
|
||||
@@ -37,7 +37,7 @@ describe("splitTelegramPlainTextChunks", () => {
|
||||
// causing the while-loop to spin forever. The surrogate pair must be
|
||||
// emitted as a unit (2 code units) so the loop always advances.
|
||||
const input = "😀X";
|
||||
const chunks = splitTelegramPlainTextChunksForTests(input, 1);
|
||||
const chunks = splitTelegramPlainTextChunks(input, 1);
|
||||
expect(chunks.join("")).toBe(input);
|
||||
for (const chunk of chunks) {
|
||||
expect(containsLoneSurrogate(chunk)).toBe(false);
|
||||
@@ -48,7 +48,7 @@ describe("splitTelegramPlainTextChunks", () => {
|
||||
// 'A' + emoji: with limit=1, second iteration starts at index 1 (high
|
||||
// surrogate) — same stall condition as above, now mid-string.
|
||||
const input = "A😀B";
|
||||
const chunks = splitTelegramPlainTextChunksForTests(input, 1);
|
||||
const chunks = splitTelegramPlainTextChunks(input, 1);
|
||||
expect(chunks.join("")).toBe(input);
|
||||
for (const chunk of chunks) {
|
||||
expect(containsLoneSurrogate(chunk)).toBe(false);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { splitTelegramPlainTextChunks } from "./rich-plain-fallback.js";
|
||||
|
||||
export { buildInlineKeyboard } from "./inline-keyboard.js";
|
||||
export {
|
||||
resetTelegramClientOptionsCacheForTests,
|
||||
@@ -21,9 +19,3 @@ export { editMessageReplyMarkupTelegram, editMessageTelegram } from "./send-edit
|
||||
export { sendLocationTelegram } from "./send-location.js";
|
||||
export { sendMessageTelegram } from "./send-message.js";
|
||||
export { sendPollTelegram, sendStickerTelegram } from "./send-special.js";
|
||||
|
||||
// Test-only handle: the plain-text splitter is internal, but its surrogate-safe
|
||||
// chunk boundary needs direct behavior coverage.
|
||||
export function splitTelegramPlainTextChunksForTests(text: string, limit: number): string[] {
|
||||
return splitTelegramPlainTextChunks(text, limit);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
import fs from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
type TokenjuicePackageManifest = {
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
type TokenjuicePluginManifest = {
|
||||
contracts?: {
|
||||
agentToolResultMiddleware?: string[];
|
||||
@@ -13,14 +9,6 @@ type TokenjuicePluginManifest = {
|
||||
};
|
||||
|
||||
describe("tokenjuice package manifest", () => {
|
||||
it("keeps runtime dependencies in the package manifest", () => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"),
|
||||
) as TokenjuicePackageManifest;
|
||||
|
||||
expect(packageJson.dependencies?.tokenjuice).toBe("0.8.1");
|
||||
});
|
||||
|
||||
it("declares runtime-neutral tool result middleware ownership in the manifest contract", () => {
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Venice tests cover models plugin behavior.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import {
|
||||
buildOpenAICompatibleLiveModelProviderConfig,
|
||||
clearLiveCatalogCacheForTests,
|
||||
@@ -131,18 +130,6 @@ describe("venice-models", () => {
|
||||
restoreDiscoveryEnv();
|
||||
});
|
||||
|
||||
it("builds static definitions with required fields", () => {
|
||||
const entry = expectDefined(VENICE_MODEL_CATALOG[0], "first Venice catalog model");
|
||||
const def = entry;
|
||||
expect(def.id).toBe(entry.id);
|
||||
expect(def.name).toBe(entry.name);
|
||||
expect(def.reasoning).toBe(entry.reasoning);
|
||||
expect(def.input).toEqual(entry.input);
|
||||
expect(def.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
|
||||
expect(def.contextWindow).toBe(entry.contextWindow);
|
||||
expect(def.maxTokens).toBe(entry.maxTokens);
|
||||
});
|
||||
|
||||
it("excludes stale models from the static fallback catalog", () => {
|
||||
const catalogIds = new Set(VENICE_MODEL_CATALOG.map((model) => model.id));
|
||||
for (const staleId of [
|
||||
|
||||
@@ -9,7 +9,6 @@ export * from "./html-tags.js";
|
||||
export * from "./ir.js";
|
||||
export * from "./render-aware-chunking.js";
|
||||
export * from "./render-attributed.js";
|
||||
export * from "./render-plain.js";
|
||||
export * from "./render.js";
|
||||
export * from "./tables.js";
|
||||
export * from "./types.js";
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { applyConstructFallbacks } from "./construct-fallbacks.js";
|
||||
import type { FormatCapabilityProfile } from "./format-capabilities.js";
|
||||
import type { MarkdownIR } from "./ir.js";
|
||||
|
||||
export type PlainRenderOptions = {
|
||||
linkStyle?: "label" | "label-and-url";
|
||||
};
|
||||
|
||||
/** Projects Markdown IR to plain text, optionally applying channel capability fallbacks. */
|
||||
export function renderMarkdownAsPlainText(
|
||||
ir: MarkdownIR,
|
||||
options: PlainRenderOptions = {},
|
||||
profile?: FormatCapabilityProfile,
|
||||
): string {
|
||||
const effectiveProfile =
|
||||
profile && options.linkStyle === "label"
|
||||
? { ...profile, constructs: { ...profile.constructs, linkLabel: "strip" as const } }
|
||||
: profile;
|
||||
const projected = effectiveProfile ? applyConstructFallbacks(ir, effectiveProfile) : ir;
|
||||
if ((options.linkStyle ?? "label-and-url") === "label" || projected.links.length === 0) {
|
||||
return projected.text;
|
||||
}
|
||||
let output = "";
|
||||
let cursor = 0;
|
||||
for (const link of [...projected.links].toSorted((a, b) => a.start - b.start)) {
|
||||
if (link.start < cursor) {
|
||||
continue;
|
||||
}
|
||||
output += projected.text.slice(cursor, link.end);
|
||||
const href = link.href.trim();
|
||||
const label = projected.text.slice(link.start, link.end).trim();
|
||||
const comparableHref = href.startsWith("mailto:") ? href.slice("mailto:".length) : href;
|
||||
if (href && label && label !== href && label !== comparableHref) {
|
||||
output += ` (${href})`;
|
||||
}
|
||||
cursor = link.end;
|
||||
}
|
||||
return output + projected.text.slice(cursor);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest";
|
||||
import type { FormatCapabilityProfile } from "./format-capabilities.js";
|
||||
import { markdownToIR } from "./ir.js";
|
||||
import { renderMarkdownWithAttributedRanges } from "./render-attributed.js";
|
||||
import { renderMarkdownAsPlainText } from "./render-plain.js";
|
||||
import { renderMarkdownWithMarkers } from "./render.js";
|
||||
|
||||
const ALL_NATIVE = {
|
||||
@@ -72,16 +71,4 @@ describe("format capability driver plumbing", () => {
|
||||
renderMarkdownWithAttributedRanges(ir, options),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps plain projection byte-identical for an all-native optional profile", () => {
|
||||
expect(renderMarkdownAsPlainText(ir, {}, ALL_NATIVE)).toBe(renderMarkdownAsPlainText(ir));
|
||||
});
|
||||
|
||||
it("keeps explicit label-only link projection above profile fallback", () => {
|
||||
const profile = {
|
||||
...ALL_NATIVE,
|
||||
constructs: { ...ALL_NATIVE.constructs, linkLabel: "fallback" as const },
|
||||
};
|
||||
expect(renderMarkdownAsPlainText(ir, { linkStyle: "label" }, profile)).toBe("See docs");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Normalization Core tests cover record coerce behavior.
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { asNullableRecord, asOptionalRecord } from "./record-coerce.js";
|
||||
|
||||
@@ -13,13 +11,4 @@ describe("record-coerce", () => {
|
||||
expect(asNullableRecord(null)).toBeNull();
|
||||
expect(asNullableRecord([{ ok: true }])).toBeNull();
|
||||
});
|
||||
|
||||
it("stays isolated from utils.ts so browser bundles stay Node-free", () => {
|
||||
const source = readFileSync(
|
||||
path.resolve("packages/normalization-core/src/record-coerce.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(source).not.toContain("../utils.js");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,12 +157,12 @@ const boundaryChecker = createExtensionImportBoundaryChecker({
|
||||
});
|
||||
|
||||
/** Cached inventory of src/plugins imports that cross into bundled extensions. */
|
||||
export const collectPluginExtensionImportBoundaryInventory = boundaryChecker.collectInventory;
|
||||
const collectPluginExtensionImportBoundaryInventory = boundaryChecker.collectInventory;
|
||||
|
||||
/**
|
||||
* Cached expected plugin-extension import inventory baseline.
|
||||
*/
|
||||
export const readExpectedInventory = createCachedAsync(
|
||||
const readExpectedInventory = createCachedAsync(
|
||||
async (): Promise<PluginExtensionInventoryEntry[]> =>
|
||||
JSON.parse(await fs.readFile(baselinePath, "utf8")),
|
||||
);
|
||||
@@ -170,7 +170,7 @@ export const readExpectedInventory = createCachedAsync(
|
||||
/**
|
||||
* Diffs expected and actual plugin-extension boundary inventory entries.
|
||||
*/
|
||||
export function diffInventory(
|
||||
function diffInventory(
|
||||
expected: PluginExtensionInventoryEntry[],
|
||||
actual: PluginExtensionInventoryEntry[],
|
||||
) {
|
||||
|
||||
@@ -12,10 +12,6 @@ const checker = createExtensionImportBoundaryChecker({
|
||||
inventoryTitle: "Test-helper extension import boundary inventory:",
|
||||
});
|
||||
|
||||
/**
|
||||
* Collects test-helper extension import boundary inventory.
|
||||
*/
|
||||
export const collectTestHelperExtensionImportBoundaryInventory = checker.collectInventory;
|
||||
/**
|
||||
* Entrypoint for the test-helper extension import boundary checker.
|
||||
*/
|
||||
|
||||
@@ -79,7 +79,7 @@ let webFetchProviderViolationsPromise:
|
||||
/**
|
||||
* Collects web-fetch provider boundary violations in core source files.
|
||||
*/
|
||||
export async function collectWebFetchProviderBoundaryViolations() {
|
||||
async function collectWebFetchProviderBoundaryViolations() {
|
||||
if (!webFetchProviderViolationsPromise) {
|
||||
webFetchProviderViolationsPromise = scanWebFetchProviderBoundaryViolations();
|
||||
try {
|
||||
|
||||
@@ -179,7 +179,7 @@ function scanGenericCoreImports(
|
||||
/**
|
||||
* Collects web-search provider boundary inventory from core source files.
|
||||
*/
|
||||
export async function collectWebSearchProviderBoundaryInventory() {
|
||||
async function collectWebSearchProviderBoundaryInventory() {
|
||||
if (!webSearchProviderInventoryPromise) {
|
||||
webSearchProviderInventoryPromise = (async () => {
|
||||
const inventory: InventoryEntry[] = [];
|
||||
@@ -213,7 +213,7 @@ export async function collectWebSearchProviderBoundaryInventory() {
|
||||
/**
|
||||
* Reads the expected web-search provider boundary inventory baseline.
|
||||
*/
|
||||
export async function readExpectedInventory(): Promise<InventoryEntry[]> {
|
||||
async function readExpectedInventory(): Promise<InventoryEntry[]> {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(await fs.readFile(baselinePath, "utf8"));
|
||||
const result = z.array(inventoryEntrySchema).safeParse(parsed);
|
||||
@@ -229,7 +229,7 @@ export async function readExpectedInventory(): Promise<InventoryEntry[]> {
|
||||
/**
|
||||
* Diffs expected and actual web-search provider boundary inventory entries.
|
||||
*/
|
||||
export function diffInventory(expected: InventoryEntry[], actual: InventoryEntry[]) {
|
||||
function diffInventory(expected: InventoryEntry[], actual: InventoryEntry[]) {
|
||||
return diffInventoryEntries(expected, actual, compareInventoryEntries);
|
||||
}
|
||||
|
||||
|
||||
@@ -2510,7 +2510,6 @@ const SEMANTIC_TOOLING_TARGET_PATTERNS: Array<[RegExp, string[]]> = [
|
||||
[
|
||||
/^scripts\/e2e\/session-runtime-context-docker\.sh$/u,
|
||||
[
|
||||
"docker-e2e-clients",
|
||||
dockerE2e,
|
||||
"src/agents/embedded-agent-runner/run/runtime-context-prompt.test.ts",
|
||||
"src/agents/embedded-agent-runner/transcript-rewrite.test.ts",
|
||||
@@ -2539,12 +2538,7 @@ const SEMANTIC_TOOLING_TARGET_PATTERNS: Array<[RegExp, string[]]> = [
|
||||
[/^scripts\/e2e\/live-plugin-tool-docker\.sh$/u, ["live-plugin-tool-assertions"]],
|
||||
[
|
||||
/^scripts\/e2e\/commitments-safety-docker\.sh$/u,
|
||||
[
|
||||
"docker-e2e-clients",
|
||||
dockerE2e,
|
||||
"src/commitments/runtime.test.ts",
|
||||
"src/commitments/store.test.ts",
|
||||
],
|
||||
[dockerE2e, "src/commitments/runtime.test.ts", "src/commitments/store.test.ts"],
|
||||
],
|
||||
[/^scripts\/e2e\/onboard-docker\.sh$/u, [dockerBuild, "openclaw-test-state"]],
|
||||
[
|
||||
@@ -2636,12 +2630,11 @@ const SEMANTIC_TOOLING_TARGET_PATTERNS: Array<[RegExp, string[]]> = [
|
||||
],
|
||||
[
|
||||
/^scripts\/e2e\/commitments-safety-docker(?:-client)?\.(?:sh|ts)$/u,
|
||||
["docker-e2e-clients", "src/commitments/runtime.test.ts", "src/commitments/store.test.ts"],
|
||||
["src/commitments/runtime.test.ts", "src/commitments/store.test.ts"],
|
||||
],
|
||||
[
|
||||
/^scripts\/e2e\/session-runtime-context-docker(?:-client)?\.(?:sh|ts)$/u,
|
||||
[
|
||||
"docker-e2e-clients",
|
||||
"src/agents/embedded-agent-runner/run/runtime-context-prompt.test.ts",
|
||||
"src/agents/embedded-agent-runner/transcript-rewrite.test.ts",
|
||||
],
|
||||
|
||||
@@ -9,9 +9,9 @@ import { resolveStorePath } from "../config/sessions/paths.js";
|
||||
import { listSessionEntriesReadOnly } from "../config/sessions/session-accessor.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatDurationCompact } from "../infra/format-time/format-duration.js";
|
||||
import { parseAgentSessionKey, type ParsedAgentSessionKey } from "../routing/session-key.js";
|
||||
import {
|
||||
formatDurationCompact,
|
||||
formatTokenUsageDisplay,
|
||||
resolveTotalTokens,
|
||||
truncateLine,
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Tests for thinking block detection.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isThinkingLikeBlock } from "./thinking-block.js";
|
||||
|
||||
describe("isThinkingLikeBlock", () => {
|
||||
it("returns true for thinking type", () => {
|
||||
expect(isThinkingLikeBlock({ type: "thinking" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for redacted_thinking type", () => {
|
||||
expect(isThinkingLikeBlock({ type: "redacted_thinking" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for non-thinking type", () => {
|
||||
expect(isThinkingLikeBlock({ type: "text" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for missing type field", () => {
|
||||
expect(isThinkingLikeBlock({ content: "hello" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for null", () => {
|
||||
expect(isThinkingLikeBlock(null)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for undefined", () => {
|
||||
expect(isThinkingLikeBlock(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for string", () => {
|
||||
expect(isThinkingLikeBlock("thinking")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty object", () => {
|
||||
expect(isThinkingLikeBlock({})).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { SubagentRunRecord } from "../subagent-registry.types.js";
|
||||
import "./agents-wait-tool.js";
|
||||
|
||||
type WaitError = { runId: string; error: "not_found" | "not_owner" };
|
||||
type WaitTarget = { runId: string; entry: SubagentRunRecord };
|
||||
type AgentsWaitToolTestApi = {
|
||||
testing: {
|
||||
ownsRun(entry: SubagentRunRecord, currentSessionKeys: ReadonlySet<string>): boolean;
|
||||
readResolvedWaitState(targets: readonly WaitTarget[], errors: readonly WaitError[]): unknown;
|
||||
readWaitState(ids: readonly string[], currentSessionKeys: ReadonlySet<string>): unknown;
|
||||
resolveWaitTargets(
|
||||
ids: readonly string[],
|
||||
currentSessionKeys: ReadonlySet<string>,
|
||||
): { targets: WaitTarget[]; errors: WaitError[] };
|
||||
waitForCollector(params: {
|
||||
ids: readonly string[];
|
||||
currentSessionKeys: ReadonlySet<string>;
|
||||
timeoutMs: number;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
function getTestApi(): AgentsWaitToolTestApi {
|
||||
return (globalThis as Record<PropertyKey, unknown>)[
|
||||
Symbol.for("openclaw.agentsWaitToolTestApi")
|
||||
] as AgentsWaitToolTestApi;
|
||||
}
|
||||
|
||||
export const testing = getTestApi().testing;
|
||||
@@ -29,7 +29,6 @@ vi.mock("../subagent-registry-state.js", () => ({
|
||||
|
||||
import { isToolResultError } from "../tool-result-error.js";
|
||||
import { createAgentsWaitTool, waitForCollectorCompletion } from "./agents-wait-tool.js";
|
||||
import { testing } from "./agents-wait-tool.test-support.js";
|
||||
|
||||
function collectorRun(
|
||||
runId: string,
|
||||
@@ -120,11 +119,6 @@ describe("agents_wait", () => {
|
||||
expect(registryEvents.listeners.size).toBe(0);
|
||||
});
|
||||
|
||||
it("exposes ownership helpers through test support", () => {
|
||||
const entry = collectorRun("owned", "agent:main:main");
|
||||
expect(testing.ownsRun(entry, new Set(["agent:main:main"]))).toBe(true);
|
||||
});
|
||||
|
||||
it("returns the first completed child and leaves siblings pending", async () => {
|
||||
records.set("one", collectorRun("one", "agent:main:main"));
|
||||
records.set("two", collectorRun("two", "agent:main:main"));
|
||||
|
||||
@@ -245,17 +245,3 @@ export function createAgentsWaitTool(opts: {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const testing = {
|
||||
ownsRun,
|
||||
readResolvedWaitState,
|
||||
readWaitState,
|
||||
resolveWaitTargets,
|
||||
waitForCollector,
|
||||
};
|
||||
|
||||
if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
||||
(globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.agentsWaitToolTestApi")] = {
|
||||
testing,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import { getSubagentRunsSnapshotForRead } from "../../../agents/subagent-registr
|
||||
import { resolveSubagentDisplayStatus } from "../../../agents/subagent-session-metrics.js";
|
||||
import { resolveStorePath } from "../../../config/sessions/paths.js";
|
||||
import { loadSessionEntryReadOnly } from "../../../config/sessions/session-accessor.js";
|
||||
import { formatDurationCompact } from "../../../infra/format-time/format-duration.js";
|
||||
import { formatTimeAgo } from "../../../infra/format-time/format-relative.ts";
|
||||
import { parseAgentSessionKey } from "../../../routing/session-key.js";
|
||||
import { formatDurationCompact } from "../../../shared/subagents-format.js";
|
||||
import { findTaskByRunIdForOwner } from "../../../tasks/task-owner-access.js";
|
||||
import { sanitizeTaskStatusText } from "../../../tasks/task-status.js";
|
||||
import type { CommandHandlerResult } from "../commands-types.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerUsersCli, testApi } from "./users-cli.js";
|
||||
import { registerUsersCli } from "./users-cli.js";
|
||||
|
||||
const callGatewayFromCli = vi.hoisted(() => vi.fn());
|
||||
vi.mock("./gateway-rpc.js", () => ({ callGatewayFromCli }));
|
||||
@@ -91,23 +91,4 @@ describe("registerUsersCli", () => {
|
||||
|
||||
expect(output).toHaveBeenCalledWith('{\n "profile": {\n "id": "p-1"\n }\n}\n');
|
||||
});
|
||||
|
||||
it("escapes untrusted profile fields in human list output", () => {
|
||||
const output = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
|
||||
testApi.writeUsersList(
|
||||
{
|
||||
profiles: [
|
||||
{
|
||||
id: "p-1",
|
||||
displayName: "Ada\n\t\u001b[2J\u0007",
|
||||
emails: ["ada@example.com\nnext@example.com"],
|
||||
},
|
||||
],
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
expect(output).toHaveBeenCalledWith("p-1\tAda\\n\\t\tada@example.com\\nnext@example.com\n");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,5 +75,3 @@ export function registerUsersCli(program: Command) {
|
||||
|
||||
applyParentDefaultHelpAction(users);
|
||||
}
|
||||
|
||||
export const testApi = { writeUsersList };
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// Audits heartbeat config coverage across inventory entries.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
auditConfigHonorInventory,
|
||||
listSchemaLeafKeysForPrefixes,
|
||||
} from "../../test/helpers/config/config-honor-audit.js";
|
||||
import {
|
||||
HEARTBEAT_CONFIG_HONOR_INVENTORY,
|
||||
HEARTBEAT_CONFIG_PREFIXES,
|
||||
} from "../../test/helpers/config/heartbeat-config-honor.inventory.js";
|
||||
|
||||
const EXPECTED_HEARTBEAT_KEYS = [
|
||||
"every",
|
||||
"model",
|
||||
"prompt",
|
||||
"timeoutSeconds",
|
||||
"lightContext",
|
||||
"isolatedSession",
|
||||
"target",
|
||||
"to",
|
||||
"accountId",
|
||||
"directPolicy",
|
||||
] as const;
|
||||
|
||||
describe("heartbeat config-honor inventory", () => {
|
||||
it("keeps the planned heartbeat audit slice aligned with schema leaf keys", () => {
|
||||
const schemaKeys = listSchemaLeafKeysForPrefixes([...HEARTBEAT_CONFIG_PREFIXES]);
|
||||
for (const key of EXPECTED_HEARTBEAT_KEYS) {
|
||||
expect(schemaKeys).toContain(key);
|
||||
}
|
||||
});
|
||||
|
||||
it("covers the planned heartbeat keys with runtime, reload, and test proofs", () => {
|
||||
const audit = auditConfigHonorInventory({
|
||||
prefixes: [...HEARTBEAT_CONFIG_PREFIXES],
|
||||
expectedKeys: [...EXPECTED_HEARTBEAT_KEYS],
|
||||
rows: HEARTBEAT_CONFIG_HONOR_INVENTORY,
|
||||
});
|
||||
|
||||
expect(audit.missingKeys).toStrictEqual([]);
|
||||
expect(audit.extraKeys).toStrictEqual([]);
|
||||
expect(audit.missingSchemaPaths).toStrictEqual([]);
|
||||
expect(audit.missingFiles).toStrictEqual([]);
|
||||
expect(audit.missingProofs).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseCronPacingBounds } from "./pacing.js";
|
||||
|
||||
describe("parseCronPacingBounds", () => {
|
||||
it("rejects pacing without a minimum or maximum", () => {
|
||||
expect(() => parseCronPacingBounds({})).toThrow(
|
||||
"cron pacing requires at least one of min or max",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
// Verifies provider capability contracts for media-generation runtimes.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "../plugins/contracts/inventory/bundled-capability-metadata.js";
|
||||
|
||||
const EXPECTED_BUNDLED_VIDEO_PROVIDER_PLUGIN_IDS = [
|
||||
"alibaba",
|
||||
"byteplus",
|
||||
"comfy",
|
||||
"deepinfra",
|
||||
"fal",
|
||||
"google",
|
||||
"minimax",
|
||||
"openai",
|
||||
"openrouter",
|
||||
"pixverse",
|
||||
"qwen",
|
||||
"runway",
|
||||
"together",
|
||||
"vydra",
|
||||
"xai",
|
||||
] as const;
|
||||
|
||||
const EXPECTED_BUNDLED_MUSIC_PROVIDER_PLUGIN_IDS = [
|
||||
"comfy",
|
||||
"fal",
|
||||
"google",
|
||||
"minimax",
|
||||
"openrouter",
|
||||
] as const;
|
||||
|
||||
const EXPECTED_BUNDLED_VIDEO_PROVIDER_IDS_BY_PLUGIN: Record<string, readonly string[]> = {
|
||||
minimax: ["minimax", "minimax-portal"],
|
||||
};
|
||||
|
||||
const EXPECTED_BUNDLED_MUSIC_PROVIDER_IDS_BY_PLUGIN: Record<string, readonly string[]> = {
|
||||
minimax: ["minimax", "minimax-portal"],
|
||||
};
|
||||
|
||||
function bundledVideoProviderPluginIds(): string[] {
|
||||
return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.videoGenerationProviderIds.length > 0,
|
||||
)
|
||||
.map((entry) => entry.pluginId)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function bundledMusicProviderPluginIds(): string[] {
|
||||
return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(entry) => entry.musicGenerationProviderIds.length > 0,
|
||||
)
|
||||
.map((entry) => entry.pluginId)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
describe("bundled media-generation provider capabilities", () => {
|
||||
it("tracks every bundled video-generation provider manifest", () => {
|
||||
expect(bundledVideoProviderPluginIds()).toEqual(EXPECTED_BUNDLED_VIDEO_PROVIDER_PLUGIN_IDS);
|
||||
for (const entry of BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(snapshot) => snapshot.videoGenerationProviderIds.length > 0,
|
||||
)) {
|
||||
expect(entry.videoGenerationProviderIds, entry.pluginId).toEqual(
|
||||
EXPECTED_BUNDLED_VIDEO_PROVIDER_IDS_BY_PLUGIN[entry.pluginId] ?? [entry.pluginId],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("tracks every bundled music-generation provider manifest", () => {
|
||||
expect(bundledMusicProviderPluginIds()).toEqual(EXPECTED_BUNDLED_MUSIC_PROVIDER_PLUGIN_IDS);
|
||||
for (const entry of BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
|
||||
(snapshot) => snapshot.musicGenerationProviderIds.length > 0,
|
||||
)) {
|
||||
expect(entry.musicGenerationProviderIds, entry.pluginId).toEqual(
|
||||
EXPECTED_BUNDLED_MUSIC_PROVIDER_IDS_BY_PLUGIN[entry.pluginId] ?? [entry.pluginId],
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -9,14 +9,12 @@ export const pluginRegistrationContractCases = {
|
||||
alibaba: {
|
||||
pluginId: "alibaba",
|
||||
videoGenerationProviderIds: ["alibaba"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
anthropic: {
|
||||
pluginId: "anthropic",
|
||||
providerIds: ["anthropic"],
|
||||
mediaUnderstandingProviderIds: ["anthropic"],
|
||||
cliBackendIds: ["claude-cli"],
|
||||
requireDescribeImages: true,
|
||||
},
|
||||
brave: {
|
||||
pluginId: "brave",
|
||||
@@ -26,7 +24,6 @@ export const pluginRegistrationContractCases = {
|
||||
pluginId: "byteplus",
|
||||
providerIds: ["byteplus", "byteplus-plan"],
|
||||
videoGenerationProviderIds: ["byteplus"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
comfy: {
|
||||
pluginId: "comfy",
|
||||
@@ -34,8 +31,6 @@ export const pluginRegistrationContractCases = {
|
||||
imageGenerationProviderIds: ["comfy"],
|
||||
musicGenerationProviderIds: ["comfy"],
|
||||
videoGenerationProviderIds: ["comfy"],
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
deepgram: {
|
||||
pluginId: "deepgram",
|
||||
@@ -48,7 +43,6 @@ export const pluginRegistrationContractCases = {
|
||||
elevenlabs: {
|
||||
pluginId: "elevenlabs",
|
||||
speechProviderIds: ["elevenlabs"],
|
||||
requireSpeechVoices: true,
|
||||
},
|
||||
exa: {
|
||||
pluginId: "exa",
|
||||
@@ -60,8 +54,6 @@ export const pluginRegistrationContractCases = {
|
||||
imageGenerationProviderIds: ["fal"],
|
||||
musicGenerationProviderIds: ["fal"],
|
||||
videoGenerationProviderIds: ["fal"],
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
firecrawl: {
|
||||
pluginId: "firecrawl",
|
||||
@@ -78,9 +70,6 @@ export const pluginRegistrationContractCases = {
|
||||
mediaUnderstandingProviderIds: ["google"],
|
||||
imageGenerationProviderIds: ["google"],
|
||||
videoGenerationProviderIds: ["google"],
|
||||
requireDescribeImages: true,
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
gradium: {
|
||||
pluginId: "gradium",
|
||||
@@ -97,7 +86,6 @@ export const pluginRegistrationContractCases = {
|
||||
microsoft: {
|
||||
pluginId: "microsoft",
|
||||
speechProviderIds: ["microsoft"],
|
||||
requireSpeechVoices: true,
|
||||
},
|
||||
minimax: {
|
||||
pluginId: "minimax",
|
||||
@@ -108,9 +96,6 @@ export const pluginRegistrationContractCases = {
|
||||
musicGenerationProviderIds: ["minimax", "minimax-portal"],
|
||||
videoGenerationProviderIds: ["minimax", "minimax-portal"],
|
||||
webSearchProviderIds: ["minimax"],
|
||||
requireDescribeImages: true,
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
mistral: {
|
||||
pluginId: "mistral",
|
||||
@@ -121,7 +106,6 @@ export const pluginRegistrationContractCases = {
|
||||
providerIds: ["moonshot"],
|
||||
webSearchProviderIds: ["kimi"],
|
||||
mediaUnderstandingProviderIds: ["moonshot"],
|
||||
requireDescribeImages: true,
|
||||
manifestAuthChoice: {
|
||||
pluginId: "kimi",
|
||||
choiceId: "kimi-code-api-key",
|
||||
@@ -157,22 +141,16 @@ export const pluginRegistrationContractCases = {
|
||||
mediaUnderstandingProviderIds: ["openai"],
|
||||
imageGenerationProviderIds: ["openai"],
|
||||
videoGenerationProviderIds: ["openai"],
|
||||
requireSpeechVoices: true,
|
||||
requireDescribeImages: true,
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
"opencode-go": {
|
||||
pluginId: "opencode-go",
|
||||
providerIds: ["opencode-go"],
|
||||
mediaUnderstandingProviderIds: ["opencode-go"],
|
||||
requireDescribeImages: true,
|
||||
},
|
||||
opencode: {
|
||||
pluginId: "opencode",
|
||||
providerIds: ["opencode"],
|
||||
mediaUnderstandingProviderIds: ["opencode"],
|
||||
requireDescribeImages: true,
|
||||
},
|
||||
openrouter: {
|
||||
pluginId: "openrouter",
|
||||
@@ -181,9 +159,6 @@ export const pluginRegistrationContractCases = {
|
||||
imageGenerationProviderIds: ["openrouter"],
|
||||
musicGenerationProviderIds: ["openrouter"],
|
||||
videoGenerationProviderIds: ["openrouter"],
|
||||
requireDescribeImages: true,
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
parallel: {
|
||||
pluginId: "parallel",
|
||||
@@ -196,7 +171,6 @@ export const pluginRegistrationContractCases = {
|
||||
pixverse: {
|
||||
pluginId: "pixverse",
|
||||
videoGenerationProviderIds: ["pixverse"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
qwen: {
|
||||
pluginId: "qwen",
|
||||
@@ -210,13 +184,10 @@ export const pluginRegistrationContractCases = {
|
||||
],
|
||||
mediaUnderstandingProviderIds: ["qwen"],
|
||||
videoGenerationProviderIds: ["qwen"],
|
||||
requireDescribeImages: true,
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
runway: {
|
||||
pluginId: "runway",
|
||||
videoGenerationProviderIds: ["runway"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
senseaudio: {
|
||||
pluginId: "senseaudio",
|
||||
@@ -231,7 +202,6 @@ export const pluginRegistrationContractCases = {
|
||||
pluginId: "together",
|
||||
providerIds: ["together"],
|
||||
videoGenerationProviderIds: ["together"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
"tts-local-cli": {
|
||||
pluginId: "tts-local-cli",
|
||||
@@ -243,9 +213,6 @@ export const pluginRegistrationContractCases = {
|
||||
speechProviderIds: ["vydra"],
|
||||
imageGenerationProviderIds: ["vydra"],
|
||||
videoGenerationProviderIds: ["vydra"],
|
||||
requireSpeechVoices: true,
|
||||
requireGenerateImage: true,
|
||||
requireGenerateVideo: true,
|
||||
manifestAuthChoice: {
|
||||
pluginId: "vydra",
|
||||
choiceId: "vydra-api-key",
|
||||
@@ -263,11 +230,9 @@ export const pluginRegistrationContractCases = {
|
||||
mediaUnderstandingProviderIds: ["xai"],
|
||||
videoGenerationProviderIds: ["xai"],
|
||||
toolNames: ["code_execution", "x_search"],
|
||||
requireGenerateVideo: true,
|
||||
},
|
||||
zai: {
|
||||
pluginId: "zai",
|
||||
mediaUnderstandingProviderIds: ["zai"],
|
||||
requireDescribeImages: true,
|
||||
},
|
||||
} satisfies Record<string, PluginRegistrationContractParams>;
|
||||
|
||||
@@ -20,10 +20,6 @@ type PluginRegistrationContractParams = {
|
||||
videoGenerationProviderIds?: string[];
|
||||
musicGenerationProviderIds?: string[];
|
||||
toolNames?: string[];
|
||||
requireSpeechVoices?: boolean;
|
||||
requireDescribeImages?: boolean;
|
||||
requireGenerateImage?: boolean;
|
||||
requireGenerateVideo?: boolean;
|
||||
manifestAuthChoice?: {
|
||||
pluginId: string;
|
||||
choiceId: string;
|
||||
|
||||
@@ -79,6 +79,7 @@ const packageManifestContractTests: PackageManifestContractParams[] = [
|
||||
{ pluginId: "synology-chat", minHostVersionBaseline: "2026.3.22" },
|
||||
{ pluginId: "telegram" },
|
||||
{ pluginId: "tlon", minHostVersionBaseline: "2026.3.22" },
|
||||
{ pluginId: "tokenjuice", pluginLocalRuntimeDeps: ["tokenjuice"] },
|
||||
{ pluginId: "twitch", minHostVersionBaseline: "2026.3.22" },
|
||||
{ pluginId: "voice-call", minHostVersionBaseline: "2026.3.22" },
|
||||
{
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { pluginRegistrationContractCases } from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
import { describePluginRegistrationContract } from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
|
||||
describePluginRegistrationContract(pluginRegistrationContractCases.parallel);
|
||||
@@ -1,11 +1,6 @@
|
||||
// Subagent format tests cover concise subagent status and duration formatting.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
formatDurationCompact,
|
||||
formatTokenUsageDisplay,
|
||||
resolveTotalTokens,
|
||||
truncateLine,
|
||||
} from "./subagents-format.js";
|
||||
import { formatTokenUsageDisplay, resolveTotalTokens, truncateLine } from "./subagents-format.js";
|
||||
|
||||
const freshUsage = (totalTokens: number) => ({
|
||||
totalTokens,
|
||||
@@ -14,16 +9,6 @@ const freshUsage = (totalTokens: number) => ({
|
||||
});
|
||||
|
||||
describe("shared/subagents-format", () => {
|
||||
it("re-exports the canonical formatter with second-level precision", () => {
|
||||
expect(formatDurationCompact()).toBeUndefined();
|
||||
expect(formatDurationCompact(30_000)).toBe("30s");
|
||||
expect(formatDurationCompact(90_000)).toBe("1m30s");
|
||||
expect(formatDurationCompact(60 * 60_000)).toBe("1h");
|
||||
expect(formatDurationCompact(61 * 60_000)).toBe("1h1m");
|
||||
expect(formatDurationCompact(24 * 60 * 60_000)).toBe("1d");
|
||||
expect(formatDurationCompact(25 * 60 * 60_000)).toBe("1d1h");
|
||||
});
|
||||
|
||||
it("formats token counts with integer, kilo, and million branches", () => {
|
||||
expect(formatTokenUsageDisplay()).toBe("");
|
||||
expect(formatTokenUsageDisplay(freshUsage(999.9))).toBe("tokens 999 prompt/cache");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Subagent formatting helpers expose compact durations and status text.
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
export { formatDurationCompact } from "../infra/format-time/format-duration.ts";
|
||||
|
||||
/** Formats token counts using compact k/m suffixes for subagent summaries. */
|
||||
function formatTokenShort(value?: number) {
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// Config honor audit helper checks config fields against expected consumers.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { computeBaseConfigSchemaResponse } from "../../../src/config/schema-base.js";
|
||||
|
||||
// Config honor audit helpers that compare schema keys with proof inventories.
|
||||
|
||||
/** Inventory row describing where one config key is declared, merged, consumed, and tested. */
|
||||
export type ConfigHonorInventoryRow = {
|
||||
key: string;
|
||||
schemaPaths: string[];
|
||||
typePaths: string[];
|
||||
mergePaths: string[];
|
||||
consumerPaths: string[];
|
||||
reloadPaths: string[];
|
||||
testPaths: string[];
|
||||
notes?: string[];
|
||||
};
|
||||
|
||||
type ConfigHonorProofKey =
|
||||
| "schemaPaths"
|
||||
| "typePaths"
|
||||
| "mergePaths"
|
||||
| "consumerPaths"
|
||||
| "reloadPaths"
|
||||
| "testPaths";
|
||||
|
||||
/** Result of auditing one config honor inventory. */
|
||||
type ConfigHonorAuditResult = {
|
||||
schemaKeys: string[];
|
||||
missingKeys: string[];
|
||||
extraKeys: string[];
|
||||
missingSchemaPaths: string[];
|
||||
missingFiles: string[];
|
||||
missingProofs: Array<{
|
||||
key: string;
|
||||
missing: ConfigHonorProofKey[];
|
||||
}>;
|
||||
};
|
||||
|
||||
const REPO_ROOT = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const BASE_CONFIG_SCHEMA = computeBaseConfigSchemaResponse({
|
||||
generatedAt: "2026-05-05T00:00:00.000Z",
|
||||
});
|
||||
|
||||
/** Return true when a dotted schema path exists in the generated base config schema. */
|
||||
function hasSchemaPath(schemaPath: string): boolean {
|
||||
const segments = schemaPath.split(".");
|
||||
let current: unknown = BASE_CONFIG_SCHEMA.schema;
|
||||
for (const segment of segments) {
|
||||
if (!current || typeof current !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (segment === "*") {
|
||||
const wildcardTarget =
|
||||
(current as { additionalProperties?: unknown; items?: unknown }).items ??
|
||||
(current as { additionalProperties?: unknown }).additionalProperties;
|
||||
if (!wildcardTarget || typeof wildcardTarget !== "object") {
|
||||
return false;
|
||||
}
|
||||
current = wildcardTarget;
|
||||
continue;
|
||||
}
|
||||
const properties = (current as { properties?: Record<string, unknown> }).properties;
|
||||
if (!properties || !Object.hasOwn(properties, segment)) {
|
||||
return false;
|
||||
}
|
||||
current = properties[segment];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** List leaf schema keys for the requested config prefixes. */
|
||||
export function listSchemaLeafKeysForPrefixes(prefixes: string[]): string[] {
|
||||
const keys = new Set<string>();
|
||||
for (const prefix of prefixes) {
|
||||
const segments = prefix.split(".");
|
||||
let current: unknown = BASE_CONFIG_SCHEMA.schema;
|
||||
for (const segment of segments) {
|
||||
if (!current || typeof current !== "object") {
|
||||
current = null;
|
||||
break;
|
||||
}
|
||||
if (segment === "*") {
|
||||
current =
|
||||
(current as { additionalProperties?: unknown; items?: unknown }).items ??
|
||||
(current as { additionalProperties?: unknown }).additionalProperties ??
|
||||
null;
|
||||
continue;
|
||||
}
|
||||
current = (current as { properties?: Record<string, unknown> }).properties?.[segment] ?? null;
|
||||
}
|
||||
const properties = (current as { properties?: Record<string, unknown> } | null)?.properties;
|
||||
if (!properties) {
|
||||
continue;
|
||||
}
|
||||
for (const key of Object.keys(properties)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
return [...keys].toSorted();
|
||||
}
|
||||
|
||||
/** Audit an inventory against schema keys, proof paths, and file existence. */
|
||||
export function auditConfigHonorInventory(params: {
|
||||
prefixes: string[];
|
||||
rows: ConfigHonorInventoryRow[];
|
||||
expectedKeys?: string[];
|
||||
repoRoot?: string;
|
||||
}): ConfigHonorAuditResult {
|
||||
const repoRoot = params.repoRoot ?? REPO_ROOT;
|
||||
const schemaKeys = listSchemaLeafKeysForPrefixes(params.prefixes);
|
||||
const expectedKeys = new Set(params.expectedKeys ?? schemaKeys);
|
||||
const rowKeys = new Set(params.rows.map((row) => row.key));
|
||||
const missingKeys = [...expectedKeys].filter((key) => !rowKeys.has(key)).toSorted();
|
||||
const extraKeys = params.rows
|
||||
.map((row) => row.key)
|
||||
.filter((key) => !expectedKeys.has(key))
|
||||
.toSorted();
|
||||
|
||||
const missingSchemaPaths = params.rows.flatMap((row) =>
|
||||
row.schemaPaths.filter((schemaPath) => !hasSchemaPath(schemaPath)),
|
||||
);
|
||||
|
||||
const missingFiles = params.rows.flatMap((row) => {
|
||||
const files = [...row.typePaths, ...row.mergePaths, ...row.consumerPaths, ...row.testPaths];
|
||||
return files
|
||||
.filter((relativePath) => !fs.existsSync(path.join(repoRoot, relativePath)))
|
||||
.map((relativePath) => `${row.key}:${relativePath}`);
|
||||
});
|
||||
|
||||
const missingProofs = params.rows
|
||||
.map((row) => {
|
||||
const missing: ConfigHonorProofKey[] = [
|
||||
row.schemaPaths.length === 0 ? "schemaPaths" : null,
|
||||
row.typePaths.length === 0 ? "typePaths" : null,
|
||||
row.mergePaths.length === 0 ? "mergePaths" : null,
|
||||
row.consumerPaths.length === 0 ? "consumerPaths" : null,
|
||||
row.reloadPaths.length === 0 ? "reloadPaths" : null,
|
||||
row.testPaths.length === 0 ? "testPaths" : null,
|
||||
].filter((value): value is ConfigHonorProofKey => value !== null);
|
||||
return missing.length > 0 ? { key: row.key, missing } : null;
|
||||
})
|
||||
.filter((row): row is NonNullable<typeof row> => row !== null);
|
||||
|
||||
return {
|
||||
schemaKeys,
|
||||
missingKeys,
|
||||
extraKeys,
|
||||
missingSchemaPaths,
|
||||
missingFiles,
|
||||
missingProofs,
|
||||
};
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// Heartbeat config honor inventory lists heartbeat config ownership rows.
|
||||
import type { ConfigHonorInventoryRow } from "./config-honor-audit.js";
|
||||
|
||||
// Inventory of heartbeat config keys and the proof paths that should honor them.
|
||||
|
||||
/** Config prefixes audited for heartbeat key coverage. */
|
||||
export const HEARTBEAT_CONFIG_PREFIXES = [
|
||||
"agents.defaults.heartbeat",
|
||||
"agents.entries.*.heartbeat",
|
||||
] as const;
|
||||
|
||||
/** Heartbeat config honor inventory consumed by config audit tests. */
|
||||
export const HEARTBEAT_CONFIG_HONOR_INVENTORY: ConfigHonorInventoryRow[] = [
|
||||
{
|
||||
key: "every",
|
||||
schemaPaths: ["agents.defaults.heartbeat.every", "agents.entries.*.heartbeat.every"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts", "src/agents/acp-spawn.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts", "src/agents/acp-spawn.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/infra/heartbeat-runner.returns-default-unset.test.ts",
|
||||
"src/gateway/config-reload.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "model",
|
||||
schemaPaths: ["agents.defaults.heartbeat.model", "agents.entries.*.heartbeat.model"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/infra/heartbeat-runner.model-override.test.ts",
|
||||
"src/gateway/config-reload.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "prompt",
|
||||
schemaPaths: ["agents.defaults.heartbeat.prompt", "agents.entries.*.heartbeat.prompt"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: ["src/infra/heartbeat-runner.returns-default-unset.test.ts"],
|
||||
},
|
||||
{
|
||||
key: "timeoutSeconds",
|
||||
schemaPaths: [
|
||||
"agents.defaults.heartbeat.timeoutSeconds",
|
||||
"agents.entries.*.heartbeat.timeoutSeconds",
|
||||
],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts", "src/auto-reply/reply/get-reply.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/config/zod-schema.agent-defaults.test.ts",
|
||||
"src/infra/heartbeat-runner.model-override.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "lightContext",
|
||||
schemaPaths: [
|
||||
"agents.defaults.heartbeat.lightContext",
|
||||
"agents.entries.*.heartbeat.lightContext",
|
||||
],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts", "src/agents/bootstrap-files.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/infra/heartbeat-runner.model-override.test.ts",
|
||||
"src/agents/bootstrap-files.test.ts",
|
||||
"src/gateway/config-reload.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "isolatedSession",
|
||||
schemaPaths: [
|
||||
"agents.defaults.heartbeat.isolatedSession",
|
||||
"agents.entries.*.heartbeat.isolatedSession",
|
||||
],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts"],
|
||||
consumerPaths: ["src/infra/heartbeat-runner.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: ["src/infra/heartbeat-runner.model-override.test.ts"],
|
||||
},
|
||||
{
|
||||
key: "target",
|
||||
schemaPaths: ["agents.defaults.heartbeat.target", "agents.entries.*.heartbeat.target"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts", "src/infra/outbound/targets.ts"],
|
||||
consumerPaths: ["src/infra/outbound/targets.ts", "src/infra/heartbeat-runner.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/infra/heartbeat-runner.returns-default-unset.test.ts",
|
||||
"src/cron/service.main-job-passes-heartbeat-target-last.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "to",
|
||||
schemaPaths: ["agents.defaults.heartbeat.to", "agents.entries.*.heartbeat.to"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts", "src/infra/outbound/targets.ts"],
|
||||
consumerPaths: ["src/infra/outbound/targets.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: ["src/infra/heartbeat-runner.returns-default-unset.test.ts"],
|
||||
},
|
||||
{
|
||||
key: "accountId",
|
||||
schemaPaths: ["agents.defaults.heartbeat.accountId", "agents.entries.*.heartbeat.accountId"],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts", "src/infra/outbound/targets.ts"],
|
||||
consumerPaths: ["src/infra/outbound/targets.ts", "src/infra/heartbeat-runner.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: [
|
||||
"src/infra/heartbeat-runner.returns-default-unset.test.ts",
|
||||
"src/infra/heartbeat-runner.ack-token-heartbeat-acks.test.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "directPolicy",
|
||||
schemaPaths: [
|
||||
"agents.defaults.heartbeat.directPolicy",
|
||||
"agents.entries.*.heartbeat.directPolicy",
|
||||
],
|
||||
typePaths: ["src/config/types.agent-defaults.ts", "src/config/zod-schema.agent-runtime.ts"],
|
||||
mergePaths: ["src/infra/heartbeat-runner.ts", "src/infra/outbound/targets.ts"],
|
||||
consumerPaths: ["src/infra/outbound/targets.ts"],
|
||||
reloadPaths: ["src/gateway/config-reload-plan.ts"],
|
||||
testPaths: ["src/infra/heartbeat-runner.returns-default-unset.test.ts"],
|
||||
},
|
||||
];
|
||||
@@ -2,11 +2,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectPluginExtensionImportBoundaryInventory,
|
||||
diffInventory,
|
||||
main,
|
||||
} from "../scripts/check-plugin-extension-import-boundary.mts";
|
||||
import { main } from "../scripts/check-plugin-extension-import-boundary.mts";
|
||||
import { createCapturedIo } from "./helpers/captured-io.js";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
@@ -18,65 +14,7 @@ const baselinePath = path.join(
|
||||
);
|
||||
const baseline = JSON.parse(readFileSync(baselinePath, "utf8"));
|
||||
|
||||
function collectInventoryFiles(
|
||||
inventory: Awaited<ReturnType<typeof collectPluginExtensionImportBoundaryInventory>>,
|
||||
predicate: (file: string) => boolean,
|
||||
): string[] {
|
||||
const files: string[] = [];
|
||||
for (const entry of inventory) {
|
||||
if (predicate(entry.file)) {
|
||||
files.push(entry.file);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
describe("plugin extension import boundary inventory", () => {
|
||||
it("keeps dedicated web-search registry shims out of the remaining inventory", async () => {
|
||||
const inventory = await collectPluginExtensionImportBoundaryInventory();
|
||||
const blockedShimFiles = collectInventoryFiles(
|
||||
inventory,
|
||||
(file) =>
|
||||
file === "src/plugins/web-search-providers.ts" ||
|
||||
file === "src/plugins/bundled-web-search-registry.ts",
|
||||
);
|
||||
|
||||
expect(blockedShimFiles).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("ignores boundary shims by scope", async () => {
|
||||
const inventory = await collectPluginExtensionImportBoundaryInventory();
|
||||
const boundaryShimFiles = collectInventoryFiles(
|
||||
inventory,
|
||||
(file) => file.startsWith("src/plugin-sdk/") || file.startsWith("src/plugin-sdk-internal/"),
|
||||
);
|
||||
|
||||
expect(boundaryShimFiles).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("produces stable sorted output", async () => {
|
||||
const first = await collectPluginExtensionImportBoundaryInventory();
|
||||
const second = await collectPluginExtensionImportBoundaryInventory();
|
||||
|
||||
expect(second).toEqual(first);
|
||||
expect(
|
||||
[...first].toSorted(
|
||||
(left, right) =>
|
||||
left.file.localeCompare(right.file) ||
|
||||
left.line - right.line ||
|
||||
left.kind.localeCompare(right.kind) ||
|
||||
left.specifier.localeCompare(right.specifier) ||
|
||||
left.reason.localeCompare(right.reason),
|
||||
),
|
||||
).toEqual(first);
|
||||
});
|
||||
|
||||
it("matches the checked-in baseline", async () => {
|
||||
const actual = await collectPluginExtensionImportBoundaryInventory();
|
||||
|
||||
expect(diffInventory(baseline, actual)).toEqual({ missing: [], unexpected: [] });
|
||||
});
|
||||
|
||||
it("script json output matches the baseline exactly", async () => {
|
||||
const captured = createCapturedIo();
|
||||
const exitCode = await main(["--json"], captured.io);
|
||||
|
||||
@@ -10,11 +10,6 @@ import {
|
||||
} from "../../scripts/e2e/cron-mcp-cleanup-docker-client.ts";
|
||||
|
||||
describe("cron MCP cleanup docker client", () => {
|
||||
it("binds a device identity for the UI-mode gateway client", () => {
|
||||
const source = fs.readFileSync("scripts/e2e/cron-mcp-cleanup-docker-client.ts", "utf8");
|
||||
expect(source).toContain("bindFreshDevice: true");
|
||||
});
|
||||
|
||||
it("rejects malformed probe pid wait limits", () => {
|
||||
expect(readCronMcpCleanupProbePidWaitMs({})).toBe(120_000);
|
||||
expect(readCronMcpCleanupProbePidWaitMs({ OPENCLAW_CRON_MCP_CLEANUP_PID_WAIT_MS: "250" })).toBe(
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Docker E2E client tests cover packaged-dist harness wiring.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
function readScript(pathname: string): string {
|
||||
return readFileSync(pathname, "utf8");
|
||||
}
|
||||
|
||||
describe("Docker E2E client scripts", () => {
|
||||
it("keeps commitments safety checks wired to packaged commitment runtime", () => {
|
||||
const source = readScript("scripts/e2e/commitments-safety-docker-client.ts");
|
||||
|
||||
expect(source).toContain("../../dist/commitments/runtime.js");
|
||||
expect(source).toContain("../../dist/commitments/store.js");
|
||||
expect(source).toContain("resetCommitmentExtractionRuntimeForTests");
|
||||
expect(source).toContain("verifyExtractionRemainsRetired()");
|
||||
expect(source).toContain("verifyDoctorImportAndRuntimeIsolation()");
|
||||
expect(source).toContain("verifyExpiryTransition()");
|
||||
expect(source).toContain('[entry, "doctor", "--fix", "--yes", "--force"]');
|
||||
expect(source).toContain("CALL_TOOL");
|
||||
});
|
||||
|
||||
it("keeps session runtime-context checks wired to packaged transcript behavior", () => {
|
||||
const source = readScript("scripts/e2e/session-runtime-context-docker-client.ts");
|
||||
|
||||
expect(source).toContain("openclaw/plugin-sdk/agent-sessions");
|
||||
expect(source).toContain(
|
||||
"../../dist/agents/embedded-agent-runner/run/runtime-context-prompt.js",
|
||||
);
|
||||
expect(source).toContain("SessionManager.inMemory()");
|
||||
expect(source).toContain("verifyRuntimeContextTranscriptShape()");
|
||||
expect(source).toContain("verifyDoctorRepair(root)");
|
||||
expect(source).toContain("<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>");
|
||||
expect(source).toContain("openclaw.runtime-context");
|
||||
expect(source).toContain("doctor repair left runtime context in active transcript");
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,6 @@ import { describe, expect, it } from "vitest";
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const scriptPath = path.join(repoRoot, "scripts/lib/openclaw-test-state.mts");
|
||||
const onboardDockerScriptPath = path.join(repoRoot, "scripts/e2e/onboard-docker.sh");
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/gu, `'\\''`)}'`;
|
||||
@@ -299,16 +298,4 @@ describe("scripts/lib/openclaw-test-state", () => {
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps onboard Docker temp homes on the shared test-state helper", async () => {
|
||||
const scriptText = await fs.readFile(onboardDockerScriptPath, "utf8");
|
||||
const scenarioText = await fs.readFile("scripts/e2e/lib/onboard/scenario.sh", "utf8");
|
||||
|
||||
expect(scriptText).toContain("OPENCLAW_TEST_STATE_FUNCTION_B64");
|
||||
expect(scriptText).toContain("scripts/e2e/lib/onboard/scenario.sh");
|
||||
expect(scenarioText).toContain("set_isolated_openclaw_env local-basic");
|
||||
expect(scenarioText).toContain("run_wizard_cmd channels channels");
|
||||
expect(scriptText).not.toContain("make_home");
|
||||
expect(scenarioText).not.toContain("make_home");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,17 +222,6 @@ describe("plugins Docker assertions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes ClawHub preflight body timeouts into the bounded reader", () => {
|
||||
const script = readFileSync(ASSERTIONS_SCRIPT, "utf8");
|
||||
|
||||
expect(script).toContain("run(controller.signal, timeoutPromise)");
|
||||
expect(
|
||||
script.match(
|
||||
/readBoundedResponseText\([\s\S]*?limits\.bodyMaxBytes,\n\s+\{ createTooLargeError: createBoundedResponseTooLargeError, timeoutPromise \},/gu,
|
||||
),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("keeps sweep artifact paths aligned with the assertion scratch root", () => {
|
||||
const scripts = [
|
||||
"scripts/e2e/lib/plugins/sweep.sh",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Prompt Snapshots tests cover prompt snapshots script behavior.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -17,8 +16,6 @@ import {
|
||||
runCodexModelPromptFixtureSync,
|
||||
} from "../../scripts/sync-codex-model-prompt-fixture.js";
|
||||
import { getPluginModuleLoaderStats } from "../../src/plugins/plugin-module-loader-cache.js";
|
||||
import { expectNoReaddirSyncDuring } from "../../src/test-utils/fs-scan-assertions.js";
|
||||
import { toRepoRelativePath } from "../../src/test-utils/repo-files.js";
|
||||
import { createHappyPathPromptSnapshotFiles } from "../helpers/agents/happy-path-prompt-snapshots.js";
|
||||
import {
|
||||
CODEX_MODEL_PROMPT_FIXTURE_DIR,
|
||||
@@ -38,104 +35,11 @@ function renderedPromptSection(content: string, heading: string, nextHeading: st
|
||||
return content.slice(start, end);
|
||||
}
|
||||
|
||||
function listCommittedPromptSnapshotFiles(): string[] {
|
||||
const externalFiles = listExternalCommittedPromptSnapshotFiles();
|
||||
if (externalFiles) {
|
||||
return externalFiles;
|
||||
}
|
||||
return fs
|
||||
.readdirSync(CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR)
|
||||
.filter((entry) => entry.endsWith(".md") || entry.endsWith(".json"))
|
||||
.map((entry) => path.join(CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR, entry))
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function listExternalCommittedPromptSnapshotFiles(): string[] | null {
|
||||
return listGitCommittedPromptSnapshotFiles() ?? listFindCommittedPromptSnapshotFiles();
|
||||
}
|
||||
|
||||
function listGitCommittedPromptSnapshotFiles(): string[] | null {
|
||||
const result = spawnSync(
|
||||
"git",
|
||||
["ls-files", "--", CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
maxBuffer: 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
return result.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.endsWith(".md") || line.endsWith(".json"))
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function listFindCommittedPromptSnapshotFiles(): string[] | null {
|
||||
const result = spawnSync(
|
||||
"find",
|
||||
[
|
||||
path.join(process.cwd(), CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR),
|
||||
"-maxdepth",
|
||||
"1",
|
||||
"-type",
|
||||
"f",
|
||||
"(",
|
||||
"-name",
|
||||
"*.md",
|
||||
"-o",
|
||||
"-name",
|
||||
"*.json",
|
||||
")",
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
maxBuffer: 1024 * 1024,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
return result.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.map((filePath) => toRepoRelativePath(process.cwd(), filePath))
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
describe("happy path prompt snapshots", () => {
|
||||
it("loads the generator entrypoint used by the prompt snapshot check", () => {
|
||||
expect(createFormattedPromptSnapshotFiles).toEqual(expect.any(Function));
|
||||
});
|
||||
|
||||
it("lists committed Codex prompt snapshot artifacts without scanning directories in-process", () => {
|
||||
expectNoReaddirSyncDuring(() => {
|
||||
const committed = listCommittedPromptSnapshotFiles();
|
||||
|
||||
expect(committed.length).toBeGreaterThan(0);
|
||||
expect(committed.every((file) => file.endsWith(".md") || file.endsWith(".json"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the committed Codex prompt snapshot artifact set explicit", () => {
|
||||
expect(listCommittedPromptSnapshotFiles().map((file) => path.basename(file))).toEqual([
|
||||
"README.md",
|
||||
"codex-dynamic-tools.discord-group.json",
|
||||
"codex-dynamic-tools.heartbeat-turn.json",
|
||||
"codex-dynamic-tools.telegram-direct.json",
|
||||
"discord-group-codex-message-tool.md",
|
||||
"telegram-direct-codex-message-tool.md",
|
||||
"telegram-heartbeat-codex-tool.md",
|
||||
]);
|
||||
});
|
||||
|
||||
it("reconstructs complete Codex tool catalogs from readable full-tool overrides", async () => {
|
||||
const generated = await createHappyPathPromptSnapshotFiles();
|
||||
const scenarios = [
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("QA CLI onboarding evidence", () => {
|
||||
it("retains the bounded redacted child log", () => {
|
||||
const source = readFileSync("scripts/e2e/qa-cli-onboarding.mjs", "utf8");
|
||||
|
||||
expect(source).toContain("writer.appendLog(chunk)");
|
||||
expect(source).toContain("const logArtifact = await writer.writeLog()");
|
||||
expect(source).toContain("artifactPaths: [logArtifact]");
|
||||
expect(source).not.toContain("artifactPaths: []");
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const FILE_SPEC_MARKER = "Testing install from npm spec (file:)...";
|
||||
|
||||
describe("QA plugin install source coverage", () => {
|
||||
it("requires the executable npm file-spec assertion marker", () => {
|
||||
const qaWrapper = readFileSync("scripts/e2e/qa-plugin-install-sources.mjs", "utf8");
|
||||
const pluginSweep = readFileSync("scripts/e2e/lib/plugins/sweep.sh", "utf8");
|
||||
|
||||
expect(pluginSweep).toContain(`echo "${FILE_SPEC_MARKER}"`);
|
||||
expect(qaWrapper).toContain(`"${FILE_SPEC_MARKER}",`);
|
||||
});
|
||||
});
|
||||
@@ -683,23 +683,19 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
"src/system-agent/audit.test.ts",
|
||||
],
|
||||
"scripts/e2e/commitments-safety-docker-client.ts": [
|
||||
"test/scripts/docker-e2e-clients.test.ts",
|
||||
"src/commitments/runtime.test.ts",
|
||||
"src/commitments/store.test.ts",
|
||||
],
|
||||
"scripts/e2e/commitments-safety-docker.sh": [
|
||||
"test/scripts/docker-e2e-clients.test.ts",
|
||||
"test/scripts/docker-e2e-plan.test.ts",
|
||||
"src/commitments/runtime.test.ts",
|
||||
"src/commitments/store.test.ts",
|
||||
],
|
||||
"scripts/e2e/session-runtime-context-docker-client.ts": [
|
||||
"test/scripts/docker-e2e-clients.test.ts",
|
||||
"src/agents/embedded-agent-runner/run/runtime-context-prompt.test.ts",
|
||||
"src/agents/embedded-agent-runner/transcript-rewrite.test.ts",
|
||||
],
|
||||
"scripts/e2e/session-runtime-context-docker.sh": [
|
||||
"test/scripts/docker-e2e-clients.test.ts",
|
||||
"test/scripts/docker-e2e-plan.test.ts",
|
||||
"src/agents/embedded-agent-runner/run/runtime-context-prompt.test.ts",
|
||||
"src/agents/embedded-agent-runner/transcript-rewrite.test.ts",
|
||||
@@ -724,10 +720,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
"src/cron/active-jobs-manual-run.test.ts",
|
||||
],
|
||||
"scripts/e2e/cron-mcp-cleanup-seed.ts": ["test/scripts/docker-e2e-seeds.test.ts"],
|
||||
"scripts/e2e/lib/onboard/scenario.sh": [
|
||||
"test/scripts/e2e-shell-tempfiles.test.ts",
|
||||
"test/scripts/openclaw-test-state.test.ts",
|
||||
],
|
||||
"scripts/e2e/lib/onboard/scenario.sh": ["test/scripts/e2e-shell-tempfiles.test.ts"],
|
||||
"scripts/e2e/lib/onboard/assert-config.mjs": ["test/scripts/onboard-config-fixtures.test.ts"],
|
||||
"scripts/e2e/lib/onboard/write-config.mjs": ["test/scripts/onboard-config-fixtures.test.ts"],
|
||||
"scripts/e2e/lib/package-compat.mjs": [
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
// Test helper extension boundary tests enforce helper import boundaries.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectTestHelperExtensionImportBoundaryInventory,
|
||||
main,
|
||||
} from "../scripts/check-test-helper-extension-import-boundary.mts";
|
||||
import { main } from "../scripts/check-test-helper-extension-import-boundary.mts";
|
||||
import { createCapturedIo } from "./helpers/captured-io.js";
|
||||
|
||||
describe("test-helper extension import boundary inventory", () => {
|
||||
it("stays empty", async () => {
|
||||
expect(await collectTestHelperExtensionImportBoundaryInventory()).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("script json output stays empty", async () => {
|
||||
const captured = createCapturedIo();
|
||||
const exitCode = await main(["--json"], captured.io);
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
// Web provider boundary tests enforce provider import boundaries.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectWebFetchProviderBoundaryViolations,
|
||||
main as webFetchMain,
|
||||
} from "../scripts/check-web-fetch-provider-boundaries.mts";
|
||||
import {
|
||||
collectWebSearchProviderBoundaryInventory,
|
||||
main as webSearchMain,
|
||||
} from "../scripts/check-web-search-provider-boundaries.mts";
|
||||
import { main as webFetchMain } from "../scripts/check-web-fetch-provider-boundaries.mts";
|
||||
import { main as webSearchMain } from "../scripts/check-web-search-provider-boundaries.mts";
|
||||
import { createCapturedIo } from "./helpers/captured-io.js";
|
||||
|
||||
const webFetchViolationsPromise = collectWebFetchProviderBoundaryViolations();
|
||||
const webFetchJsonOutputPromise = getJsonOutput(webFetchMain);
|
||||
const webSearchInventoryPromise = collectWebSearchProviderBoundaryInventory();
|
||||
const webSearchJsonOutputPromise = getJsonOutput(webSearchMain);
|
||||
|
||||
async function getJsonOutput(
|
||||
@@ -28,30 +20,17 @@ async function getJsonOutput(
|
||||
}
|
||||
|
||||
describe("web provider boundaries", () => {
|
||||
it("keeps Firecrawl-specific fetch logic out of core runtime/tooling", async () => {
|
||||
const violations = await webFetchViolationsPromise;
|
||||
it("runs the web fetch boundary script in JSON mode", async () => {
|
||||
const jsonOutput = await webFetchJsonOutputPromise;
|
||||
|
||||
expect(violations).toStrictEqual([]);
|
||||
expect(jsonOutput.exitCode).toBe(0);
|
||||
expect(jsonOutput.stderr).toBe("");
|
||||
expect(jsonOutput.json).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("keeps web search provider boundary inventory empty, core-only, and sorted", async () => {
|
||||
const inventory = await webSearchInventoryPromise;
|
||||
it("runs the web search boundary script in JSON mode", async () => {
|
||||
const jsonOutput = await webSearchJsonOutputPromise;
|
||||
|
||||
expect(inventory).toStrictEqual([]);
|
||||
expect(
|
||||
[...inventory].toSorted(
|
||||
(left, right) =>
|
||||
left.provider.localeCompare(right.provider) ||
|
||||
left.file.localeCompare(right.file) ||
|
||||
left.line - right.line ||
|
||||
left.reason.localeCompare(right.reason),
|
||||
),
|
||||
).toEqual(inventory);
|
||||
expect(jsonOutput.exitCode).toBe(0);
|
||||
expect(jsonOutput.stderr).toBe("");
|
||||
expect(jsonOutput.json).toStrictEqual([]);
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
// @vitest-environment node
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
type CommandsModule = typeof import("./commands.js");
|
||||
const browserImportPath = "./commands.ts?browser-import";
|
||||
|
||||
function importDeclarations(source: string): string[] {
|
||||
return (source.match(/^import[\s\S]*?;$/gmu) ?? []).map((declaration) =>
|
||||
declaration
|
||||
.replace(/\s+/gu, " ")
|
||||
.replace(/\{\s+/gu, "{ ")
|
||||
.replace(/,\s*\}/gu, " }")
|
||||
.replace(/\s+\}/gu, " }")
|
||||
.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
describe("slash command browser import", () => {
|
||||
it("builds fallback commands from the browser-safe shared registry", async () => {
|
||||
const mod = (await import(browserImportPath)) as CommandsModule;
|
||||
@@ -35,40 +23,4 @@ describe("slash command browser import", () => {
|
||||
source: "native",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps provider thinking runtime out of the Control UI import path", async () => {
|
||||
const commands = await readFile(new URL("./commands.ts", import.meta.url), "utf8");
|
||||
const sharedRegistry = await readFile(
|
||||
new URL("../../../../src/auto-reply/commands-registry.shared.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const serverRegistry = await readFile(
|
||||
new URL("../../../../src/auto-reply/commands-registry.data.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(importDeclarations(commands)).toEqual([
|
||||
'import { asNullableRecord as asRecord } from "@openclaw/normalization-core/record-coerce";',
|
||||
'import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";',
|
||||
'import type { CommandEntry } from "../../../../packages/gateway-protocol/src/index.js";',
|
||||
'import { buildBuiltinChatCommands } from "../../../../src/auto-reply/commands-registry.shared.js";',
|
||||
'import { t } from "../../i18n/index.ts";',
|
||||
'import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts";',
|
||||
]);
|
||||
expect(importDeclarations(sharedRegistry)).toEqual([
|
||||
'import { normalizeOptionalLowercaseString } from "../../packages/normalization-core/src/string-coerce.js";',
|
||||
'import { normalizeStringEntries } from "../../packages/normalization-core/src/string-normalization.js";',
|
||||
'import { formatFastModeAutoLabel, resolveFastModeModelAutoOnSeconds } from "../shared/fast-mode.js";',
|
||||
'import { COMMAND_ARG_FORMATTERS } from "./commands-args.js";',
|
||||
'import type { ChatCommandDefinition, CommandArgChoiceContext, CommandCategory, CommandScope, CommandTier } from "./commands-registry.types.js";',
|
||||
'import { BASE_THINKING_LEVELS, type ThinkLevel } from "./thinking.shared.js";',
|
||||
]);
|
||||
expect(importDeclarations(serverRegistry)).toEqual([
|
||||
'import { listLoadedChannelPlugins } from "../channels/plugins/registry-loaded.js";',
|
||||
'import { getActivePluginChannelRegistryVersionFromState } from "../plugins/runtime-channel-state.js";',
|
||||
'import { assertCommandRegistry, buildBuiltinChatCommands, defineChatCommand } from "./commands-registry.shared.js";',
|
||||
'import type { ChatCommandDefinition } from "./commands-registry.types.js";',
|
||||
'import { listThinkingLevels } from "./thinking.js";',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user