claw-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/dist/agent-client.d.ts +17 -0
- package/dist/agent-client.d.ts.map +1 -0
- package/dist/agent-client.js +79 -0
- package/dist/agent-client.js.map +1 -0
- package/dist/agent-client.test.d.ts +2 -0
- package/dist/agent-client.test.d.ts.map +1 -0
- package/dist/agent-client.test.js +57 -0
- package/dist/agent-client.test.js.map +1 -0
- package/dist/bench.d.ts +45 -0
- package/dist/bench.d.ts.map +1 -0
- package/dist/bench.js +133 -0
- package/dist/bench.js.map +1 -0
- package/dist/bench.test.d.ts +2 -0
- package/dist/bench.test.d.ts.map +1 -0
- package/dist/bench.test.js +95 -0
- package/dist/bench.test.js.map +1 -0
- package/dist/bin/clawbench.d.ts +11 -0
- package/dist/bin/clawbench.d.ts.map +1 -0
- package/dist/bin/clawbench.js +237 -0
- package/dist/bin/clawbench.js.map +1 -0
- package/dist/bot.d.ts +36 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +74 -0
- package/dist/bot.js.map +1 -0
- package/dist/bot.test.d.ts +2 -0
- package/dist/bot.test.d.ts.map +1 -0
- package/dist/bot.test.js +109 -0
- package/dist/bot.test.js.map +1 -0
- package/dist/cost-tracker.d.ts +10 -0
- package/dist/cost-tracker.d.ts.map +1 -0
- package/dist/cost-tracker.js +81 -0
- package/dist/cost-tracker.js.map +1 -0
- package/dist/cost-tracker.test.d.ts +2 -0
- package/dist/cost-tracker.test.d.ts.map +1 -0
- package/dist/cost-tracker.test.js +75 -0
- package/dist/cost-tracker.test.js.map +1 -0
- package/dist/docker-gateway.d.ts +40 -0
- package/dist/docker-gateway.d.ts.map +1 -0
- package/dist/docker-gateway.js +172 -0
- package/dist/docker-gateway.js.map +1 -0
- package/dist/docker-gateway.test.d.ts +2 -0
- package/dist/docker-gateway.test.d.ts.map +1 -0
- package/dist/docker-gateway.test.js +116 -0
- package/dist/docker-gateway.test.js.map +1 -0
- package/dist/gateway.d.ts +32 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +142 -0
- package/dist/gateway.js.map +1 -0
- package/dist/gateway.test.d.ts +2 -0
- package/dist/gateway.test.d.ts.map +1 -0
- package/dist/gateway.test.js +19 -0
- package/dist/gateway.test.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/runner.d.ts +13 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +213 -0
- package/dist/runner.js.map +1 -0
- package/dist/runner.test.d.ts +2 -0
- package/dist/runner.test.d.ts.map +1 -0
- package/dist/runner.test.js +298 -0
- package/dist/runner.test.js.map +1 -0
- package/dist/scenario-loader.d.ts +6 -0
- package/dist/scenario-loader.d.ts.map +1 -0
- package/dist/scenario-loader.js +77 -0
- package/dist/scenario-loader.js.map +1 -0
- package/dist/scenario-loader.test.d.ts +2 -0
- package/dist/scenario-loader.test.d.ts.map +1 -0
- package/dist/scenario-loader.test.js +213 -0
- package/dist/scenario-loader.test.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +106 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +55 -0
- package/dist/utils.test.js.map +1 -0
- package/dist/workspace.d.ts +33 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +198 -0
- package/dist/workspace.js.map +1 -0
- package/dist/workspace.test.d.ts +2 -0
- package/dist/workspace.test.d.ts.map +1 -0
- package/dist/workspace.test.js +68 -0
- package/dist/workspace.test.js.map +1 -0
- package/docker/Dockerfile +12 -0
- package/docker/entrypoint.sh +6 -0
- package/package.json +49 -0
- package/presets/configs/default.json5 +28 -0
- package/presets/configs/minimal.json5 +22 -0
- package/presets/personas/curious.md +5 -0
- package/presets/personas/friendly.md +5 -0
- package/presets/personas/terse.md +3 -0
- package/presets/scenarios/example-chat.yaml +71 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { deepMerge, parseDuration, getPackageRoot } from './utils.js';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
describe('deepMerge', () => {
|
|
6
|
+
it('merges flat objects', () => {
|
|
7
|
+
expect(deepMerge({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
|
|
8
|
+
});
|
|
9
|
+
it('merges nested objects', () => {
|
|
10
|
+
const target = { gateway: { port: 8080, auth: { token: 'old' } } };
|
|
11
|
+
const source = { gateway: { auth: { token: 'new' } } };
|
|
12
|
+
expect(deepMerge(target, source)).toEqual({
|
|
13
|
+
gateway: { port: 8080, auth: { token: 'new' } },
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
it('replaces arrays instead of concatenating', () => {
|
|
17
|
+
const target = { list: [1, 2, 3] };
|
|
18
|
+
const source = { list: [4, 5] };
|
|
19
|
+
expect(deepMerge(target, source)).toEqual({ list: [4, 5] });
|
|
20
|
+
});
|
|
21
|
+
it('skips undefined values in source', () => {
|
|
22
|
+
const target = { a: 1, b: 2 };
|
|
23
|
+
const source = { a: undefined, b: 3 };
|
|
24
|
+
expect(deepMerge(target, source)).toEqual({ a: 1, b: 3 });
|
|
25
|
+
});
|
|
26
|
+
it('does not mutate the target', () => {
|
|
27
|
+
const target = { a: 1, nested: { b: 2 } };
|
|
28
|
+
const original = { a: 1, nested: { b: 2 } };
|
|
29
|
+
deepMerge(target, { a: 99 });
|
|
30
|
+
expect(target).toEqual(original);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('parseDuration', () => {
|
|
34
|
+
it('parses milliseconds', () => {
|
|
35
|
+
expect(parseDuration('500ms')).toBe(500);
|
|
36
|
+
});
|
|
37
|
+
it('parses seconds', () => {
|
|
38
|
+
expect(parseDuration('30s')).toBe(30_000);
|
|
39
|
+
});
|
|
40
|
+
it('parses minutes', () => {
|
|
41
|
+
expect(parseDuration('2m')).toBe(120_000);
|
|
42
|
+
});
|
|
43
|
+
it('returns 60000 for invalid input', () => {
|
|
44
|
+
expect(parseDuration('invalid')).toBe(60_000);
|
|
45
|
+
expect(parseDuration('')).toBe(60_000);
|
|
46
|
+
expect(parseDuration('10x')).toBe(60_000);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('getPackageRoot', () => {
|
|
50
|
+
it('returns a path containing package.json', async () => {
|
|
51
|
+
const root = await getPackageRoot();
|
|
52
|
+
expect(existsSync(join(root, 'package.json'))).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=utils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.js","sourceRoot":"","sources":["../src/utils.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAClE,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QACtD,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACxC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;SAChD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAA;QAClC,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAA;QAC/B,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;QAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;QACrC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAA;QACzC,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAA;QAC3C,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC7C,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACtC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,IAAI,GAAG,MAAM,cAAc,EAAE,CAAA;QACnC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace — Creates and manages isolated OpenClaw workspace directories.
|
|
3
|
+
*
|
|
4
|
+
* Each bot gets a profile directory at ~/.openclaw-claw-harness-<id>/
|
|
5
|
+
* containing openclaw.json, USER.md, and skills.
|
|
6
|
+
*/
|
|
7
|
+
import type { BotConfig } from './types.js';
|
|
8
|
+
import type { BotRuntimeConfig } from './bot.js';
|
|
9
|
+
export declare class Workspace {
|
|
10
|
+
readonly profileName: string;
|
|
11
|
+
private _profileDir;
|
|
12
|
+
private workspaceSubdir;
|
|
13
|
+
private skillsDir;
|
|
14
|
+
get profileDir(): string;
|
|
15
|
+
constructor(botId: string, baseDir: string);
|
|
16
|
+
/**
|
|
17
|
+
* Create the workspace directory structure and populate files.
|
|
18
|
+
*/
|
|
19
|
+
setup(config: BotConfig, runtime: BotRuntimeConfig): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Remove the workspace directory.
|
|
22
|
+
*/
|
|
23
|
+
cleanup(): Promise<void>;
|
|
24
|
+
private buildConfig;
|
|
25
|
+
private loadPreset;
|
|
26
|
+
private installSkill;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve content that could be a file path or inline string.
|
|
29
|
+
* Tries: absolute path, cwd-relative, package-root-relative, then inline.
|
|
30
|
+
*/
|
|
31
|
+
resolveContent(pathOrContent: string): Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAGhD,qBAAa,SAAS;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,SAAS,CAAQ;IAEzB,IAAI,UAAU,IAAI,MAAM,CAA4B;gBAExC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAQ1C;;OAEG;IACG,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CxE;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAQhB,WAAW;YA2DX,UAAU;YAyBV,YAAY;IA4B1B;;;OAGG;IACG,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA4B7D"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace — Creates and manages isolated OpenClaw workspace directories.
|
|
3
|
+
*
|
|
4
|
+
* Each bot gets a profile directory at ~/.openclaw-claw-harness-<id>/
|
|
5
|
+
* containing openclaw.json, USER.md, and skills.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, writeFile, rm, readFile, access } from 'node:fs/promises';
|
|
8
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
9
|
+
import JSON5 from 'json5';
|
|
10
|
+
import { deepMerge, getPackageRoot } from './utils.js';
|
|
11
|
+
export class Workspace {
|
|
12
|
+
profileName;
|
|
13
|
+
_profileDir;
|
|
14
|
+
workspaceSubdir;
|
|
15
|
+
skillsDir;
|
|
16
|
+
get profileDir() { return this._profileDir; }
|
|
17
|
+
constructor(botId, baseDir) {
|
|
18
|
+
this.profileName = `claw-harness-${botId}`;
|
|
19
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
20
|
+
this._profileDir = join(home, `.openclaw-${this.profileName}`);
|
|
21
|
+
this.workspaceSubdir = join(this._profileDir, 'workspace');
|
|
22
|
+
this.skillsDir = join(this.workspaceSubdir, 'skills');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create the workspace directory structure and populate files.
|
|
26
|
+
*/
|
|
27
|
+
async setup(config, runtime) {
|
|
28
|
+
// Create directory structure
|
|
29
|
+
await mkdir(this.skillsDir, { recursive: true });
|
|
30
|
+
// Write openclaw.json
|
|
31
|
+
const openclawConfig = await this.buildConfig(config, runtime);
|
|
32
|
+
await writeFile(join(this._profileDir, 'openclaw.json'), JSON.stringify(openclawConfig, null, 2));
|
|
33
|
+
// Write .env file with API keys so the agent can find them
|
|
34
|
+
const envLines = [];
|
|
35
|
+
if (runtime.anthropicApiKey) {
|
|
36
|
+
envLines.push(`ANTHROPIC_API_KEY=${runtime.anthropicApiKey}`);
|
|
37
|
+
}
|
|
38
|
+
if (runtime.openaiApiKey) {
|
|
39
|
+
envLines.push(`OPENAI_API_KEY=${runtime.openaiApiKey}`);
|
|
40
|
+
}
|
|
41
|
+
if (envLines.length > 0) {
|
|
42
|
+
await writeFile(join(this._profileDir, '.env'), envLines.join('\n') + '\n');
|
|
43
|
+
}
|
|
44
|
+
// Write USER.md (persona)
|
|
45
|
+
if (config.userMd) {
|
|
46
|
+
const content = await this.resolveContent(config.userMd);
|
|
47
|
+
await writeFile(join(this.workspaceSubdir, 'USER.md'), content);
|
|
48
|
+
}
|
|
49
|
+
// Write SOUL.md (personality)
|
|
50
|
+
if (config.soulMd) {
|
|
51
|
+
const content = await this.resolveContent(config.soulMd);
|
|
52
|
+
await writeFile(join(this.workspaceSubdir, 'SOUL.md'), content);
|
|
53
|
+
}
|
|
54
|
+
// Install skills
|
|
55
|
+
if (config.skills) {
|
|
56
|
+
for (const skill of config.skills) {
|
|
57
|
+
await this.installSkill(skill);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Remove the workspace directory.
|
|
63
|
+
*/
|
|
64
|
+
async cleanup() {
|
|
65
|
+
try {
|
|
66
|
+
await rm(this._profileDir, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Best-effort cleanup
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async buildConfig(config, runtime) {
|
|
73
|
+
// Load preset if specified
|
|
74
|
+
let base = {};
|
|
75
|
+
if (config.preset) {
|
|
76
|
+
base = await this.loadPreset(config.preset);
|
|
77
|
+
}
|
|
78
|
+
// Build the config, merging preset with overrides
|
|
79
|
+
const token = `claw-harness-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
80
|
+
// Build agents defaults with optional web_fetch allowlist
|
|
81
|
+
const agentsDefaults = {};
|
|
82
|
+
if (config.model) {
|
|
83
|
+
agentsDefaults.model = { primary: config.model };
|
|
84
|
+
}
|
|
85
|
+
if (config.allowedHosts && config.allowedHosts.length > 0) {
|
|
86
|
+
agentsDefaults.web_fetch = {
|
|
87
|
+
allowed_hosts: config.allowedHosts,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
let result = deepMerge(base, {
|
|
91
|
+
gateway: {
|
|
92
|
+
mode: 'local',
|
|
93
|
+
port: runtime.port,
|
|
94
|
+
auth: { token },
|
|
95
|
+
http: {
|
|
96
|
+
endpoints: {
|
|
97
|
+
chatCompletions: { enabled: true },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
agents: {
|
|
102
|
+
defaults: agentsDefaults,
|
|
103
|
+
list: [{ id: 'main', default: true }],
|
|
104
|
+
},
|
|
105
|
+
skills: {
|
|
106
|
+
allowBundled: [],
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
// Docker mode: enable headless browser with Chromium
|
|
110
|
+
if (runtime.mode === 'docker') {
|
|
111
|
+
result = deepMerge(result, {
|
|
112
|
+
browser: { enabled: true, headless: true, noSandbox: true },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Apply user-specified overrides
|
|
116
|
+
if (config.configOverrides) {
|
|
117
|
+
return deepMerge(result, config.configOverrides);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
async loadPreset(name) {
|
|
122
|
+
const pkgRoot = await getPackageRoot();
|
|
123
|
+
// Search order: cwd presets -> package presets
|
|
124
|
+
const presetsLocations = [
|
|
125
|
+
join(process.cwd(), 'presets', 'configs', `${name}.json5`),
|
|
126
|
+
join(process.cwd(), 'presets', 'configs', `${name}.json`),
|
|
127
|
+
join(pkgRoot, 'presets', 'configs', `${name}.json5`),
|
|
128
|
+
join(pkgRoot, 'presets', 'configs', `${name}.json`),
|
|
129
|
+
];
|
|
130
|
+
for (const loc of presetsLocations) {
|
|
131
|
+
try {
|
|
132
|
+
await access(loc);
|
|
133
|
+
const content = await readFile(loc, 'utf-8');
|
|
134
|
+
return JSON5.parse(content);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
console.warn(`Warning: preset "${name}" not found, using empty config`);
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
async installSkill(skill) {
|
|
144
|
+
const skillName = skill.name ?? 'skill';
|
|
145
|
+
const skillDir = join(this.skillsDir, skillName);
|
|
146
|
+
await mkdir(skillDir, { recursive: true });
|
|
147
|
+
let content;
|
|
148
|
+
if (skill.url) {
|
|
149
|
+
// Fetch from URL with timeout
|
|
150
|
+
const response = await fetch(skill.url, {
|
|
151
|
+
signal: AbortSignal.timeout(30_000),
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(`Failed to fetch skill from ${skill.url}: ${response.status}`);
|
|
155
|
+
}
|
|
156
|
+
content = await response.text();
|
|
157
|
+
}
|
|
158
|
+
else if (skill.path) {
|
|
159
|
+
// Read from local file
|
|
160
|
+
content = await readFile(skill.path, 'utf-8');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
throw new Error('Skill must have either url or path');
|
|
164
|
+
}
|
|
165
|
+
await writeFile(join(skillDir, 'SKILL.md'), content);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve content that could be a file path or inline string.
|
|
169
|
+
* Tries: absolute path, cwd-relative, package-root-relative, then inline.
|
|
170
|
+
*/
|
|
171
|
+
async resolveContent(pathOrContent) {
|
|
172
|
+
// If it contains newlines or doesn't look like a path, treat as inline content
|
|
173
|
+
if (pathOrContent.includes('\n') || !pathOrContent.match(/\.\w+$/)) {
|
|
174
|
+
return pathOrContent;
|
|
175
|
+
}
|
|
176
|
+
// Try resolving as file path from multiple locations
|
|
177
|
+
const candidates = [];
|
|
178
|
+
if (isAbsolute(pathOrContent)) {
|
|
179
|
+
candidates.push(pathOrContent);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
candidates.push(resolve(process.cwd(), pathOrContent));
|
|
183
|
+
const pkgRoot = await getPackageRoot();
|
|
184
|
+
candidates.push(resolve(pkgRoot, pathOrContent));
|
|
185
|
+
}
|
|
186
|
+
for (const candidate of candidates) {
|
|
187
|
+
try {
|
|
188
|
+
return await readFile(candidate, 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Fall back to treating it as inline content
|
|
195
|
+
return pathOrContent;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.test.d.ts","sourceRoot":"","sources":["../src/workspace.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { Workspace } from './workspace.js';
|
|
3
|
+
import { mkdir, writeFile, readFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
const TEST_DIR = join(tmpdir(), 'claw-harness-workspace-test');
|
|
7
|
+
describe('Workspace', () => {
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
await mkdir(TEST_DIR, { recursive: true });
|
|
10
|
+
});
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
13
|
+
// Clean up any test profile directories
|
|
14
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
15
|
+
await rm(join(home, '.openclaw-claw-harness-test-ws'), { recursive: true, force: true }).catch(() => { });
|
|
16
|
+
});
|
|
17
|
+
it('resolveContent returns inline content for strings with newlines', async () => {
|
|
18
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
19
|
+
const result = await ws.resolveContent('line one\nline two');
|
|
20
|
+
expect(result).toBe('line one\nline two');
|
|
21
|
+
});
|
|
22
|
+
it('resolveContent returns inline content for strings without file extension', async () => {
|
|
23
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
24
|
+
const result = await ws.resolveContent('You are a friendly bot');
|
|
25
|
+
expect(result).toBe('You are a friendly bot');
|
|
26
|
+
});
|
|
27
|
+
it('resolveContent reads file content for valid paths', async () => {
|
|
28
|
+
const testFile = join(TEST_DIR, 'test-persona.md');
|
|
29
|
+
await writeFile(testFile, 'I am a test persona');
|
|
30
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
31
|
+
const result = await ws.resolveContent(testFile);
|
|
32
|
+
expect(result).toBe('I am a test persona');
|
|
33
|
+
});
|
|
34
|
+
it('resolveContent falls back to inline for missing file paths', async () => {
|
|
35
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
36
|
+
const result = await ws.resolveContent('/nonexistent/file.md');
|
|
37
|
+
expect(result).toBe('/nonexistent/file.md');
|
|
38
|
+
});
|
|
39
|
+
it('installSkill throws for skill with no url or path', async () => {
|
|
40
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
41
|
+
const runtime = { mode: 'local', port: 18800, workspaceDir: TEST_DIR, anthropicApiKey: 'test', openaiApiKey: '' };
|
|
42
|
+
await ws.setup({ skills: [{}] }, runtime).catch(err => {
|
|
43
|
+
expect(err.message).toBe('Skill must have either url or path');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('profileDir getter returns the profile directory path', () => {
|
|
47
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
48
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
|
|
49
|
+
expect(ws.profileDir).toBe(join(home, '.openclaw-claw-harness-test-ws'));
|
|
50
|
+
});
|
|
51
|
+
it('docker mode includes browser config in openclaw.json', async () => {
|
|
52
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
53
|
+
const runtime = { mode: 'docker', port: 18800, workspaceDir: TEST_DIR, anthropicApiKey: 'test', openaiApiKey: '' };
|
|
54
|
+
await ws.setup({}, runtime);
|
|
55
|
+
const configPath = join(ws.profileDir, 'openclaw.json');
|
|
56
|
+
const config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
57
|
+
expect(config.browser).toEqual({ enabled: true, headless: true, noSandbox: true });
|
|
58
|
+
});
|
|
59
|
+
it('local mode does not include browser config in openclaw.json', async () => {
|
|
60
|
+
const ws = new Workspace('test-ws', TEST_DIR);
|
|
61
|
+
const runtime = { mode: 'local', port: 18800, workspaceDir: TEST_DIR, anthropicApiKey: 'test', openaiApiKey: '' };
|
|
62
|
+
await ws.setup({}, runtime);
|
|
63
|
+
const configPath = join(ws.profileDir, 'openclaw.json');
|
|
64
|
+
const config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
65
|
+
expect(config.browser).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=workspace.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.test.js","sourceRoot":"","sources":["../src/workspace.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,6BAA6B,CAAC,CAAA;AAE9D,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,wCAAwC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAA;QAClE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,gCAAgC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAC1G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;QAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAA;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAA;QAClD,MAAM,SAAS,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAA;QAEhD,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;QAC1H,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;YACpD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAA;QAChE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAA;QAClE,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;QAC3H,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,CAAC,CAAA;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC7C,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;QAC1H,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,CAAC,CAAA;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
FROM node:22-slim
|
|
2
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
3
|
+
chromium xvfb fonts-liberation libatk-bridge2.0-0 libdrm2 \
|
|
4
|
+
libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 \
|
|
5
|
+
libcairo2 libasound2 libnspr4 libnss3 libxss1 libxtst6 \
|
|
6
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
7
|
+
RUN npm install -g openclaw@latest
|
|
8
|
+
ENV CHROME_PATH=/usr/bin/chromium
|
|
9
|
+
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
|
10
|
+
COPY entrypoint.sh /entrypoint.sh
|
|
11
|
+
RUN chmod +x /entrypoint.sh
|
|
12
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claw-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Testing framework for OpenClaw bots. Spin up real agents, load skills, drive multi-turn prompts, and capture results.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"claw-harness": "dist/bin/clawbench.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"lint": "tsc --noEmit",
|
|
16
|
+
"postbuild": "chmod +x dist/bin/clawbench.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"openclaw",
|
|
20
|
+
"testing",
|
|
21
|
+
"ai-agents",
|
|
22
|
+
"benchmark",
|
|
23
|
+
"bot-testing",
|
|
24
|
+
"skill-testing"
|
|
25
|
+
],
|
|
26
|
+
"files": ["dist", "presets", "docker"],
|
|
27
|
+
"author": "Connor Daly connor@iceblock.ventures",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/dasconnor/claw-harness.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/dasconnor/claw-harness#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/dasconnor/claw-harness/issues"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"json5": "^2.2.3",
|
|
42
|
+
"yaml": "^2.7.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"typescript": "^5.7.0",
|
|
47
|
+
"vitest": "^3.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Claw Harness default bot configuration
|
|
3
|
+
// Merged into each bot's openclaw.json
|
|
4
|
+
"gateway": {
|
|
5
|
+
"mode": "local",
|
|
6
|
+
// port is set dynamically by Claw Harness
|
|
7
|
+
"auth": {
|
|
8
|
+
// token is set dynamically by Claw Harness
|
|
9
|
+
},
|
|
10
|
+
"http": {
|
|
11
|
+
"endpoints": {
|
|
12
|
+
"chatCompletions": { "enabled": true }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"agents": {
|
|
17
|
+
"defaults": {
|
|
18
|
+
"model": { "primary": "anthropic/claude-haiku-4-5-20251001" }
|
|
19
|
+
},
|
|
20
|
+
"list": [
|
|
21
|
+
{ "id": "main", "default": true }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"skills": {
|
|
25
|
+
// Disable all bundled skills so only test skills are active
|
|
26
|
+
"allowBundled": []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Minimal config — restricted tools, lower cost
|
|
3
|
+
"gateway": {
|
|
4
|
+
"mode": "local",
|
|
5
|
+
"http": {
|
|
6
|
+
"endpoints": {
|
|
7
|
+
"chatCompletions": { "enabled": true }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"agents": {
|
|
12
|
+
"defaults": {
|
|
13
|
+
"model": { "primary": "anthropic/claude-haiku-4-5-20251001" }
|
|
14
|
+
},
|
|
15
|
+
"list": [
|
|
16
|
+
{ "id": "main", "default": true }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"skills": {
|
|
20
|
+
"allowBundled": []
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# About Me
|
|
2
|
+
|
|
3
|
+
I'm a curious, thoughtful AI bot. I ask interesting questions and engage deeply with conversation partners. I prefer substance over small talk and enjoy exploring ideas.
|
|
4
|
+
|
|
5
|
+
When interacting with platforms or services, I read the documentation carefully and think through my actions before taking them.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# About Me
|
|
2
|
+
|
|
3
|
+
I'm a friendly, outgoing AI bot. I enjoy meeting other bots and having casual conversations. I'm enthusiastic, ask follow-up questions, and like to share interesting observations.
|
|
4
|
+
|
|
5
|
+
When interacting with platforms or services, I explore them naturally — trying things out, reading documentation, and figuring out how things work on my own.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Example: Two bots discover a site and have a conversation
|
|
2
|
+
#
|
|
3
|
+
# Usage:
|
|
4
|
+
# claw-harness run presets/scenarios/example-chat.yaml
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# - OpenClaw installed (`npm install -g openclaw@latest`)
|
|
8
|
+
# - ANTHROPIC_API_KEY set in environment
|
|
9
|
+
# - Target app running at base_url
|
|
10
|
+
|
|
11
|
+
name: "Example Chat Test"
|
|
12
|
+
description: "Two bots read a skill.md, register, and have a conversation"
|
|
13
|
+
|
|
14
|
+
target:
|
|
15
|
+
base_url: "http://localhost:3000"
|
|
16
|
+
|
|
17
|
+
bots:
|
|
18
|
+
alpha:
|
|
19
|
+
preset: default
|
|
20
|
+
model: anthropic/claude-haiku-4-5-20251001
|
|
21
|
+
user_md: presets/personas/friendly.md
|
|
22
|
+
skills:
|
|
23
|
+
- url: "http://localhost:3000/skill.md"
|
|
24
|
+
name: target-app
|
|
25
|
+
|
|
26
|
+
beta:
|
|
27
|
+
preset: default
|
|
28
|
+
model: anthropic/claude-haiku-4-5-20251001
|
|
29
|
+
user_md: presets/personas/curious.md
|
|
30
|
+
skills:
|
|
31
|
+
- url: "http://localhost:3000/skill.md"
|
|
32
|
+
name: target-app
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
# Both bots register
|
|
36
|
+
- bot: alpha
|
|
37
|
+
prompt: >
|
|
38
|
+
Read the skill documentation to understand how this platform works.
|
|
39
|
+
Then register yourself as a bot on the platform.
|
|
40
|
+
timeout: 60s
|
|
41
|
+
|
|
42
|
+
- bot: beta
|
|
43
|
+
prompt: >
|
|
44
|
+
Read the skill documentation to understand how this platform works.
|
|
45
|
+
Then register yourself as a bot on the platform.
|
|
46
|
+
timeout: 60s
|
|
47
|
+
|
|
48
|
+
# Alpha joins a lounge
|
|
49
|
+
- bot: alpha
|
|
50
|
+
prompt: >
|
|
51
|
+
Browse the platform for active lounges or events.
|
|
52
|
+
Join one and introduce yourself.
|
|
53
|
+
timeout: 60s
|
|
54
|
+
|
|
55
|
+
# Beta follows
|
|
56
|
+
- bot: beta
|
|
57
|
+
prompt: >
|
|
58
|
+
Browse the platform for active lounges or events.
|
|
59
|
+
If you see a lounge with another bot, join it and start a conversation.
|
|
60
|
+
timeout: 60s
|
|
61
|
+
|
|
62
|
+
# Back-and-forth conversation
|
|
63
|
+
- repeat: 3
|
|
64
|
+
interval: 15s
|
|
65
|
+
steps:
|
|
66
|
+
- bot: alpha
|
|
67
|
+
prompt: "Check for new messages and respond to continue the conversation."
|
|
68
|
+
timeout: 30s
|
|
69
|
+
- bot: beta
|
|
70
|
+
prompt: "Check for new messages and respond to continue the conversation."
|
|
71
|
+
timeout: 30s
|