Skip to content

Commit aa09b24

Browse files
committed
Merge remote-tracking branch 'origin/main' into work/pr-100293-skill-workshop
2 parents 4833a98 + fd2e4da commit aa09b24

148 files changed

Lines changed: 2944 additions & 1745 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs-sync-publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
3939
with:
4040
repository: openclaw/clawhub
41+
ref: main
4142
path: clawhub-source
4243
fetch-depth: 1
4344
persist-credentials: false

.github/workflows/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ jobs:
4040
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
4141
with:
4242
repository: openclaw/clawhub
43+
ref: main
4344
path: clawhub-source
4445
fetch-depth: 1
4546
persist-credentials: false

docs/reference/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ If `pnpm test` flakes on a loaded host, rerun once before treating it as a regre
174174
- `pnpm test:perf:imports`: enables Vitest import-duration + import-breakdown reporting, while still using scoped lane routing for explicit file/directory targets. `pnpm test:perf:imports:changed` scopes the same profiling to files changed since `origin/main`.
175175
- `pnpm test:perf:changed:bench -- --ref <git-ref>` benchmarks the routed changed-mode path against the native root-project run for the same committed git diff; `pnpm test:perf:changed:bench -- --worktree` benchmarks the current worktree change set without committing first.
176176
- `pnpm test:perf:profile:main` writes a CPU profile for the Vitest main thread (`.artifacts/vitest-main-profile`); `pnpm test:perf:profile:runner` writes CPU + heap profiles for the unit runner (`.artifacts/vitest-runner-profile`).
177-
- `pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json`: runs every full-suite Vitest leaf config serially and writes grouped duration data plus per-config JSON/log artifacts. The Test Performance Agent uses this as its baseline before attempting slow-test fixes. `pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json` compares grouped reports after a performance-focused change.
177+
- `pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json`: runs every full-suite Vitest leaf config serially and writes grouped duration data plus per-config JSON/log artifacts. Full-suite reports isolate files by default so retained module graphs and GC pauses from earlier files are not charged to later assertions; pass `-- --no-isolate` only when intentionally profiling shared-worker accumulation. The Test Performance Agent uses this as its baseline before attempting slow-test fixes. `pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json` compares grouped reports after a performance-focused change.
178178
- Full, extension, and include-pattern shard runs update local timing data in `.artifacts/vitest-shard-timings.json`; later whole-config runs use those timings to balance slow and fast shards. Include-pattern CI shards append the shard name to the timing key, which keeps filtered shard timings visible without replacing whole-config timing data. Set `OPENCLAW_TEST_PROJECTS_TIMINGS=0` to ignore the local timing artifact.
179179

180180
## Benchmarks

extensions/active-memory/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,10 +2839,10 @@ describe("active-memory plugin", () => {
28392839
it("returns partial transcript text on timeout when the subagent has already written assistant output", async () => {
28402840
testing.setMinimumTimeoutMsForTests(1);
28412841
testing.setSetupGraceTimeoutMsForTests(0);
2842-
testing.setTimeoutPartialDataGraceMsForTests(200);
2842+
testing.setTimeoutPartialDataGraceMsForTests(50);
28432843
api.pluginConfig = {
28442844
agents: ["main"],
2845-
timeoutMs: 1_000,
2845+
timeoutMs: 100,
28462846
maxSummaryChars: 40,
28472847
persistTranscripts: true,
28482848
logging: true,

extensions/codex/src/app-server/attempt-startup.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe("startCodexAttemptThread", () => {
242242
});
243243

244244
it("clears the shared app-server when startup abandons an in-flight thread request", async () => {
245-
const { harness, run } = startThreadWithHarness(2_000);
245+
const { harness, run } = startThreadWithHarness(500);
246246
const runError = run.then(
247247
() => undefined,
248248
(error: unknown) => error,
@@ -253,7 +253,7 @@ describe("startCodexAttemptThread", () => {
253253
const error = await runError;
254254
await vi.waitFor(() => expect(harness.stdinDestroyed).toBe(true), {
255255
interval: 1,
256-
timeout: 2_000,
256+
timeout: 1_000,
257257
});
258258
expect(error).toBeInstanceOf(Error);
259259
expect((error as Error).message).toBe("codex app-server startup timed out");
@@ -290,7 +290,7 @@ describe("startCodexAttemptThread", () => {
290290
});
291291

292292
it("closes the shared app-server when startup times out during initialize", async () => {
293-
const { harness, run } = startThreadWithHarness(2_000);
293+
const { harness, run } = startThreadWithHarness(500);
294294
const runError = run.then(
295295
() => undefined,
296296
(error: unknown) => error,
@@ -304,7 +304,7 @@ describe("startCodexAttemptThread", () => {
304304
expect((error as Error).message).toBe("codex app-server startup timed out");
305305
await vi.waitFor(() => expect(harness.stdinDestroyed).toBe(true), {
306306
interval: 1,
307-
timeout: 2_000,
307+
timeout: 1_000,
308308
});
309309
expect(
310310
readHarnessMessages(harness.writes).some((write) => write.method === "thread/start"),

extensions/codex/src/app-server/provider-capabilities.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe("resolveCodexProviderWebSearchSupport", () => {
8585

8686
await expect(resolveSupport(clientFactory, "amazon-bedrock")).resolves.toBe("unsupported");
8787
await expect(resolveSupport(clientFactory, "custom-provider")).resolves.toBe("unsupported");
88+
await expect(resolveSupport(clientFactory, "lmstudio")).resolves.toBe("unsupported");
8889
expect(request).not.toHaveBeenCalled();
8990
});
9091
});

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5657,41 +5657,6 @@ describe("runCodexAppServerAttempt", () => {
56575657
expect(turnRequestParams?.approvalsReviewer).toBe("user");
56585658
});
56595659

5660-
it("keeps managed web_search for provider-qualified Codex model overrides", async () => {
5661-
testing.setOpenClawCodingToolsFactoryForTests(() => [createRuntimeDynamicTool("web_search")]);
5662-
const sessionFile = path.join(tempDir, "session.jsonl");
5663-
const workspaceDir = path.join(tempDir, "workspace");
5664-
const { requests, waitForMethod, completeTurn } = createStartedThreadHarness(async (method) => {
5665-
if (method === "modelProvider/capabilities/read") {
5666-
return { webSearch: true };
5667-
}
5668-
return undefined;
5669-
});
5670-
const params = createParams(sessionFile, workspaceDir);
5671-
params.disableTools = false;
5672-
params.runtimePlan = createCodexRuntimePlanFixture();
5673-
params.modelId = "lmstudio/local-model";
5674-
5675-
const run = runCodexAppServerAttempt(params);
5676-
await waitForMethod("turn/start");
5677-
await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
5678-
await run;
5679-
5680-
expect(requests.map((request) => request.method)).not.toContain(
5681-
"modelProvider/capabilities/read",
5682-
);
5683-
const startRequest = requests.find((request) => request.method === "thread/start");
5684-
const startRequestParams = startRequest?.params as Record<string, unknown> | undefined;
5685-
const startConfig = startRequestParams?.config as Record<string, unknown> | undefined;
5686-
const dynamicToolNames = specNames(
5687-
(startRequestParams?.dynamicTools as CodexDynamicToolSpec[] | undefined) ?? [],
5688-
);
5689-
expect(startRequestParams?.model).toBe("local-model");
5690-
expect(startRequestParams?.modelProvider).toBe("lmstudio");
5691-
expect(startConfig?.web_search).toBe("disabled");
5692-
expect(dynamicToolNames).toContain("web_search");
5693-
});
5694-
56955660
it("uses bound local model providers when disabling Guardian on resumed threads", async () => {
56965661
const sessionFile = path.join(tempDir, "session.jsonl");
56975662
const workspaceDir = path.join(tempDir, "workspace");

extensions/copilot/src/byok-proxy.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ export async function createCopilotByokProxy(
7777
}
7878
await new Promise<void>((resolve) => {
7979
server.close(() => resolve());
80+
// The proxy is private and ephemeral. Do not let a client's keep-alive
81+
// socket delay shutdown after every upstream request has been aborted.
82+
server.closeAllConnections();
8083
});
8184
},
8285
};

extensions/discord/src/monitor/native-command.think-autocomplete.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ describe("discord native /think autocomplete", () => {
220220
});
221221
({ findCommandByNativeName, resolveCommandArgChoices, resolveDiscordNativeChoiceContext } =
222222
await loadDiscordThinkAutocompleteModulesForTest());
223+
224+
// Compile the provider-backed default choice context outside per-case timing.
225+
const { command, levelArg } = requireThinkLevelCommand();
226+
resolveCommandArgChoices({ command, arg: levelArg, cfg: createConfig(), catalog: [] });
223227
});
224228

225229
beforeEach(async () => {

extensions/imessage/src/send.test.ts

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -963,19 +963,13 @@ describe("sendMessageIMessage receipts", () => {
963963
});
964964

965965
it("does not use the local default chat.db path for custom cliPath wrappers", async () => {
966+
vi.useFakeTimers({ now: 1_000 });
966967
vi.stubEnv("HOME", "/Users/me");
967968
const client = createRejectingClient(new Error("imsg rpc timeout (send)"));
968969
const runCliJson = vi.fn();
969970
const resolveSentMessageGuidImpl = vi.fn(async () => null);
970971
const approvalText = createApprovalText("approval-remote");
971-
const nowSpy = vi.spyOn(Date, "now");
972-
nowSpy
973-
.mockReturnValueOnce(1_000)
974-
.mockReturnValueOnce(1_000)
975-
.mockReturnValueOnce(1_000)
976-
.mockReturnValueOnce(6_001);
977-
978-
await expect(
972+
const rejection = expect(
979973
sendMessageIMessage("chat_id:42", approvalText, {
980974
config: {
981975
channels: {
@@ -994,6 +988,8 @@ describe("sendMessageIMessage receipts", () => {
994988
resolveSentMessageGuidImpl,
995989
}),
996990
).rejects.toThrow("imsg rpc timeout (send)");
991+
await vi.runAllTimersAsync();
992+
await rejection;
997993

998994
expect(runCliJson).not.toHaveBeenCalled();
999995
expect(resolveSentMessageGuidImpl).toHaveBeenCalledWith({
@@ -1005,6 +1001,7 @@ describe("sendMessageIMessage receipts", () => {
10051001
});
10061002

10071003
it("does not use the local default chat.db path for auto-detected ssh wrappers", async () => {
1004+
vi.useFakeTimers({ now: 1_000 });
10081005
vi.stubEnv("HOME", "/Users/me");
10091006
const wrapperDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-imsg-wrapper-"));
10101007
const wrapperPath = path.join(wrapperDir, "imsg");
@@ -1013,15 +1010,8 @@ describe("sendMessageIMessage receipts", () => {
10131010
const runCliJson = vi.fn();
10141011
const resolveSentMessageGuidImpl = vi.fn(async () => null);
10151012
const approvalText = createApprovalText("approval-ssh-wrapper");
1016-
const nowSpy = vi.spyOn(Date, "now");
1017-
nowSpy
1018-
.mockReturnValueOnce(1_000)
1019-
.mockReturnValueOnce(1_000)
1020-
.mockReturnValueOnce(1_000)
1021-
.mockReturnValueOnce(6_001);
1022-
10231013
try {
1024-
await expect(
1014+
const rejection = expect(
10251015
sendMessageIMessage("chat_id:42", approvalText, {
10261016
config: IMESSAGE_TEST_CFG,
10271017
client,
@@ -1030,6 +1020,8 @@ describe("sendMessageIMessage receipts", () => {
10301020
resolveSentMessageGuidImpl,
10311021
}),
10321022
).rejects.toThrow("imsg rpc timeout (send)");
1023+
await vi.runAllTimersAsync();
1024+
await rejection;
10331025
} finally {
10341026
fs.rmSync(wrapperDir, { recursive: true, force: true });
10351027
}
@@ -1063,17 +1055,11 @@ describe("sendMessageIMessage receipts", () => {
10631055
});
10641056

10651057
it("throws the rpc timeout without resending when sent-row recovery misses", async () => {
1058+
vi.useFakeTimers({ now: 1_000 });
10661059
const client = createRejectingClient(new Error("imsg rpc timeout (send)"));
10671060
const runCliJson = vi.fn();
10681061
const resolveSentMessageGuidImpl = vi.fn(async () => null);
1069-
const nowSpy = vi.spyOn(Date, "now");
1070-
nowSpy
1071-
.mockReturnValueOnce(1_000)
1072-
.mockReturnValueOnce(1_000)
1073-
.mockReturnValueOnce(1_000)
1074-
.mockReturnValueOnce(6_001);
1075-
1076-
await expect(
1062+
const rejection = expect(
10771063
sendMessageIMessage("chat_id:42", "hello", {
10781064
config: IMESSAGE_TEST_CFG,
10791065
createClient: async () => client,
@@ -1082,23 +1068,19 @@ describe("sendMessageIMessage receipts", () => {
10821068
resolveSentMessageGuidImpl,
10831069
}),
10841070
).rejects.toThrow("imsg rpc timeout (send)");
1071+
await vi.runAllTimersAsync();
1072+
await rejection;
10851073

10861074
expect(getClientMocks(client).stop).toHaveBeenCalledTimes(1);
10871075
expect(runCliJson).not.toHaveBeenCalled();
10881076
});
10891077

10901078
it("does not stop caller-owned rpc clients after sent-row recovery misses", async () => {
1079+
vi.useFakeTimers({ now: 1_000 });
10911080
const client = createRejectingClient(new Error("imsg rpc timeout (send)"));
10921081
const runCliJson = vi.fn();
10931082
const resolveSentMessageGuidImpl = vi.fn(async () => null);
1094-
const nowSpy = vi.spyOn(Date, "now");
1095-
nowSpy
1096-
.mockReturnValueOnce(1_000)
1097-
.mockReturnValueOnce(1_000)
1098-
.mockReturnValueOnce(1_000)
1099-
.mockReturnValueOnce(6_001);
1100-
1101-
await expect(
1083+
const rejection = expect(
11021084
sendMessageIMessage("chat_id:42", "hello", {
11031085
config: IMESSAGE_TEST_CFG,
11041086
client,
@@ -1107,6 +1089,8 @@ describe("sendMessageIMessage receipts", () => {
11071089
resolveSentMessageGuidImpl,
11081090
}),
11091091
).rejects.toThrow("imsg rpc timeout (send)");
1092+
await vi.runAllTimersAsync();
1093+
await rejection;
11101094

11111095
expect(runCliJson).not.toHaveBeenCalled();
11121096
expect(getClientMocks(client).stop).not.toHaveBeenCalled();
@@ -1130,18 +1114,12 @@ describe("sendMessageIMessage receipts", () => {
11301114
});
11311115

11321116
it("throws the rpc timeout without resending when approval GUID recovery misses", async () => {
1117+
vi.useFakeTimers({ now: 1_000 });
11331118
const client = createRejectingClient(new Error("imsg rpc timeout (send)"));
11341119
const runCliJson = vi.fn();
11351120
const resolveSentMessageGuidImpl = vi.fn(async () => null);
11361121
const approvalText = createApprovalText();
1137-
const nowSpy = vi.spyOn(Date, "now");
1138-
nowSpy
1139-
.mockReturnValueOnce(1_000)
1140-
.mockReturnValueOnce(1_000)
1141-
.mockReturnValueOnce(1_000)
1142-
.mockReturnValueOnce(6_001);
1143-
1144-
await expect(
1122+
const rejection = expect(
11451123
sendMessageIMessage("chat_id:42", approvalText, {
11461124
config: IMESSAGE_TEST_CFG,
11471125
client,
@@ -1150,6 +1128,8 @@ describe("sendMessageIMessage receipts", () => {
11501128
resolveSentMessageGuidImpl,
11511129
}),
11521130
).rejects.toThrow("imsg rpc timeout (send)");
1131+
await vi.runAllTimersAsync();
1132+
await rejection;
11531133

11541134
expect(runCliJson).not.toHaveBeenCalled();
11551135
expect(resolveSentMessageGuidImpl).toHaveBeenCalled();

0 commit comments

Comments
 (0)