Skip to content

Commit f717a13

Browse files
committed
refactor(agent): dedupe harness and command workflows
1 parent 04892ee commit f717a13

204 files changed

Lines changed: 7337 additions & 11511 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.

src/agents/auth-profiles.markauthprofilefailure.e2e.test.ts

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,43 @@ import {
88
markAuthProfileFailure,
99
} from "./auth-profiles.js";
1010

11-
describe("markAuthProfileFailure", () => {
12-
it("disables billing failures for ~5 hours by default", async () => {
13-
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-"));
14-
try {
15-
const authPath = path.join(agentDir, "auth-profiles.json");
16-
fs.writeFileSync(
17-
authPath,
18-
JSON.stringify({
19-
version: 1,
20-
profiles: {
21-
"anthropic:default": {
22-
type: "api_key",
23-
provider: "anthropic",
24-
key: "sk-default",
25-
},
11+
type AuthProfileStore = ReturnType<typeof ensureAuthProfileStore>;
12+
13+
async function withAuthProfileStore(
14+
fn: (ctx: { agentDir: string; store: AuthProfileStore }) => Promise<void>,
15+
): Promise<void> {
16+
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-"));
17+
try {
18+
const authPath = path.join(agentDir, "auth-profiles.json");
19+
fs.writeFileSync(
20+
authPath,
21+
JSON.stringify({
22+
version: 1,
23+
profiles: {
24+
"anthropic:default": {
25+
type: "api_key",
26+
provider: "anthropic",
27+
key: "sk-default",
2628
},
27-
}),
28-
);
29+
},
30+
}),
31+
);
2932

30-
const store = ensureAuthProfileStore(agentDir);
33+
const store = ensureAuthProfileStore(agentDir);
34+
await fn({ agentDir, store });
35+
} finally {
36+
fs.rmSync(agentDir, { recursive: true, force: true });
37+
}
38+
}
39+
40+
function expectCooldownInRange(remainingMs: number, minMs: number, maxMs: number): void {
41+
expect(remainingMs).toBeGreaterThan(minMs);
42+
expect(remainingMs).toBeLessThan(maxMs);
43+
}
44+
45+
describe("markAuthProfileFailure", () => {
46+
it("disables billing failures for ~5 hours by default", async () => {
47+
await withAuthProfileStore(async ({ agentDir, store }) => {
3148
const startedAt = Date.now();
3249
await markAuthProfileFailure({
3350
store,
@@ -39,31 +56,11 @@ describe("markAuthProfileFailure", () => {
3956
const disabledUntil = store.usageStats?.["anthropic:default"]?.disabledUntil;
4057
expect(typeof disabledUntil).toBe("number");
4158
const remainingMs = (disabledUntil as number) - startedAt;
42-
expect(remainingMs).toBeGreaterThan(4.5 * 60 * 60 * 1000);
43-
expect(remainingMs).toBeLessThan(5.5 * 60 * 60 * 1000);
44-
} finally {
45-
fs.rmSync(agentDir, { recursive: true, force: true });
46-
}
59+
expectCooldownInRange(remainingMs, 4.5 * 60 * 60 * 1000, 5.5 * 60 * 60 * 1000);
60+
});
4761
});
4862
it("honors per-provider billing backoff overrides", async () => {
49-
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-"));
50-
try {
51-
const authPath = path.join(agentDir, "auth-profiles.json");
52-
fs.writeFileSync(
53-
authPath,
54-
JSON.stringify({
55-
version: 1,
56-
profiles: {
57-
"anthropic:default": {
58-
type: "api_key",
59-
provider: "anthropic",
60-
key: "sk-default",
61-
},
62-
},
63-
}),
64-
);
65-
66-
const store = ensureAuthProfileStore(agentDir);
63+
await withAuthProfileStore(async ({ agentDir, store }) => {
6764
const startedAt = Date.now();
6865
await markAuthProfileFailure({
6966
store,
@@ -83,11 +80,8 @@ describe("markAuthProfileFailure", () => {
8380
const disabledUntil = store.usageStats?.["anthropic:default"]?.disabledUntil;
8481
expect(typeof disabledUntil).toBe("number");
8582
const remainingMs = (disabledUntil as number) - startedAt;
86-
expect(remainingMs).toBeGreaterThan(0.8 * 60 * 60 * 1000);
87-
expect(remainingMs).toBeLessThan(1.2 * 60 * 60 * 1000);
88-
} finally {
89-
fs.rmSync(agentDir, { recursive: true, force: true });
90-
}
83+
expectCooldownInRange(remainingMs, 0.8 * 60 * 60 * 1000, 1.2 * 60 * 60 * 1000);
84+
});
9185
});
9286
it("resets backoff counters outside the failure window", async () => {
9387
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-"));

src/agents/auth-profiles.resolve-auth-profile-order.normalizes-z-ai-aliases-auth-order.e2e.test.ts

Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
11
import { describe, expect, it } from "vitest";
2-
import { resolveAuthProfileOrder } from "./auth-profiles.js";
2+
import { type AuthProfileStore, resolveAuthProfileOrder } from "./auth-profiles.js";
3+
4+
function makeApiKeyStore(provider: string, profileIds: string[]): AuthProfileStore {
5+
return {
6+
version: 1,
7+
profiles: Object.fromEntries(
8+
profileIds.map((profileId) => [
9+
profileId,
10+
{
11+
type: "api_key",
12+
provider,
13+
key: profileId.endsWith(":work") ? "sk-work" : "sk-default",
14+
},
15+
]),
16+
),
17+
};
18+
}
19+
20+
function makeApiKeyProfilesByProviderProvider(
21+
providerByProfileId: Record<string, string>,
22+
): Record<string, { provider: string; mode: "api_key" }> {
23+
return Object.fromEntries(
24+
Object.entries(providerByProfileId).map(([profileId, provider]) => [
25+
profileId,
26+
{ provider, mode: "api_key" },
27+
]),
28+
);
29+
}
330

431
describe("resolveAuthProfileOrder", () => {
532
it("normalizes z.ai aliases in auth.order", () => {
633
const order = resolveAuthProfileOrder({
734
cfg: {
835
auth: {
936
order: { "z.ai": ["zai:work", "zai:default"] },
10-
profiles: {
11-
"zai:default": { provider: "zai", mode: "api_key" },
12-
"zai:work": { provider: "zai", mode: "api_key" },
13-
},
14-
},
15-
},
16-
store: {
17-
version: 1,
18-
profiles: {
19-
"zai:default": {
20-
type: "api_key",
21-
provider: "zai",
22-
key: "sk-default",
23-
},
24-
"zai:work": {
25-
type: "api_key",
26-
provider: "zai",
27-
key: "sk-work",
28-
},
37+
profiles: makeApiKeyProfilesByProviderProvider({
38+
"zai:default": "zai",
39+
"zai:work": "zai",
40+
}),
2941
},
3042
},
43+
store: makeApiKeyStore("zai", ["zai:default", "zai:work"]),
3144
provider: "zai",
3245
});
3346
expect(order).toEqual(["zai:work", "zai:default"]);
@@ -37,27 +50,13 @@ describe("resolveAuthProfileOrder", () => {
3750
cfg: {
3851
auth: {
3952
order: { OpenAI: ["openai:work", "openai:default"] },
40-
profiles: {
41-
"openai:default": { provider: "openai", mode: "api_key" },
42-
"openai:work": { provider: "openai", mode: "api_key" },
43-
},
44-
},
45-
},
46-
store: {
47-
version: 1,
48-
profiles: {
49-
"openai:default": {
50-
type: "api_key",
51-
provider: "openai",
52-
key: "sk-default",
53-
},
54-
"openai:work": {
55-
type: "api_key",
56-
provider: "openai",
57-
key: "sk-work",
58-
},
53+
profiles: makeApiKeyProfilesByProviderProvider({
54+
"openai:default": "openai",
55+
"openai:work": "openai",
56+
}),
5957
},
6058
},
59+
store: makeApiKeyStore("openai", ["openai:default", "openai:work"]),
6160
provider: "openai",
6261
});
6362
expect(order).toEqual(["openai:work", "openai:default"]);
@@ -66,27 +65,13 @@ describe("resolveAuthProfileOrder", () => {
6665
const order = resolveAuthProfileOrder({
6766
cfg: {
6867
auth: {
69-
profiles: {
70-
"zai:default": { provider: "z.ai", mode: "api_key" },
71-
"zai:work": { provider: "Z.AI", mode: "api_key" },
72-
},
73-
},
74-
},
75-
store: {
76-
version: 1,
77-
profiles: {
78-
"zai:default": {
79-
type: "api_key",
80-
provider: "zai",
81-
key: "sk-default",
82-
},
83-
"zai:work": {
84-
type: "api_key",
85-
provider: "zai",
86-
key: "sk-work",
87-
},
68+
profiles: makeApiKeyProfilesByProviderProvider({
69+
"zai:default": "z.ai",
70+
"zai:work": "Z.AI",
71+
}),
8872
},
8973
},
74+
store: makeApiKeyStore("zai", ["zai:default", "zai:work"]),
9075
provider: "zai",
9176
});
9277
expect(order).toEqual(["zai:default", "zai:work"]);

src/agents/bash-tools.e2e.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ async function waitForCompletion(sessionId: string) {
6262
return status;
6363
}
6464

65+
async function runBackgroundEchoLines(lines: string[]) {
66+
const result = await execTool.execute("call1", {
67+
command: echoLines(lines),
68+
background: true,
69+
});
70+
const sessionId = (result.details as { sessionId: string }).sessionId;
71+
await waitForCompletion(sessionId);
72+
return sessionId;
73+
}
74+
6575
beforeEach(() => {
6676
resetProcessRegistryForTests();
6777
resetSystemEventsForTest();
@@ -223,12 +233,7 @@ describe("exec tool backgrounding", () => {
223233

224234
it("defaults process log to a bounded tail when no window is provided", async () => {
225235
const lines = Array.from({ length: 260 }, (_value, index) => `line-${index + 1}`);
226-
const result = await execTool.execute("call1", {
227-
command: echoLines(lines),
228-
background: true,
229-
});
230-
const sessionId = (result.details as { sessionId: string }).sessionId;
231-
await waitForCompletion(sessionId);
236+
const sessionId = await runBackgroundEchoLines(lines);
232237

233238
const log = await processTool.execute("call2", {
234239
action: "log",
@@ -263,12 +268,7 @@ describe("exec tool backgrounding", () => {
263268

264269
it("keeps offset-only log requests unbounded by default tail mode", async () => {
265270
const lines = Array.from({ length: 260 }, (_value, index) => `line-${index + 1}`);
266-
const result = await execTool.execute("call1", {
267-
command: echoLines(lines),
268-
background: true,
269-
});
270-
const sessionId = (result.details as { sessionId: string }).sessionId;
271-
await waitForCompletion(sessionId);
271+
const sessionId = await runBackgroundEchoLines(lines);
272272

273273
const log = await processTool.execute("call2", {
274274
action: "log",

0 commit comments

Comments
 (0)