@vostride/agent-qa-runner-local 0.2.2 → 0.2.4
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 +78 -0
- package/dist/index.cjs +78 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -31
- package/dist/index.d.ts +14 -31
- package/dist/index.js +79 -24
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-dark.jpg">
|
|
3
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-light.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-light.png" alt="agent-qa by vostride — a self-improving QA agent for your software factory" width="100%">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>English</strong> · <a href="https://github.com/vostride/agent-qa/blob/main/README.zh-CN.md">中文</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/agent-qa"><img src="https://img.shields.io/npm/v/agent-qa?color=196872" alt="npm version"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/agent-qa"><img src="https://img.shields.io/npm/dm/agent-qa?color=196872" alt="npm downloads per month"></a>
|
|
14
|
+
<a href="https://github.com/vostride/agent-qa/stargazers"><img src="https://img.shields.io/github/stars/vostride/agent-qa?color=196872" alt="GitHub stars"></a>
|
|
15
|
+
<a href="https://github.com/vostride/agent-qa/actions/workflows/ci.yml"><img src="https://github.com/vostride/agent-qa/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
|
16
|
+
<a href="https://vostride.com/docs/agent-qa"><img src="https://img.shields.io/badge/docs-agent--qa-196872" alt="Documentation"></a>
|
|
17
|
+
<a href="https://github.com/vostride/agent-qa/blob/main/LICENSE.md"><img src="https://img.shields.io/badge/license-FSL--1.1--ALv2-196872" alt="License: FSL-1.1-ALv2"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://vostride.com/agent-qa">Website</a> · <a href="https://vostride.com/docs/agent-qa">Docs</a> · <a href="https://vostride.com/demo/agent-qa">Demo</a> · <a href="https://vostride.com/blog">Blog</a> · <a href="https://vostride.com/changelog">Changelog</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
**Your AI QA engineer, with memory.** Describe a user journey in plain language. agent-qa tests it on web, Android or iOS and keeps the evidence. Use the CLI, the dashboard, or your coding agent through MCP and skills.
|
|
25
|
+
|
|
26
|
+
- **memidex:** remembers facts and working procedures as reviewable files in your repository.
|
|
27
|
+
- **Self-improvement:** validates lessons from runs, retires stale knowledge, and uses your feedback to correct mistakes.
|
|
28
|
+
|
|
29
|
+
| Benchmark | Rank | Published result |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| [AndroidWorld](https://vostride.com/benchmark/android-world) | **#1** | **100% pass@1** (116/116 tasks) |
|
|
32
|
+
| [AppControlBench](https://vostride.com/benchmark/app-control-bench) | **#1** | **99.6% completion** (114.5/115 points) |
|
|
33
|
+
|
|
34
|
+
Read the full benchmark reports, with run evidence and methodology, on [our blog](https://vostride.com/blog). Both used gpt-6-sol with memory and self-improvement off; AppControlBench gives half credit for partial completion.
|
|
35
|
+
|
|
36
|
+
## See it in action
|
|
37
|
+
|
|
38
|
+
**Inspect a failure.** The demo catches a cart showing $29 instead of $53, with the failed step and its screenshot side by side.
|
|
39
|
+
|
|
40
|
+
<picture>
|
|
41
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-dark.png">
|
|
42
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-light.png">
|
|
43
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-light.png" alt="The dashboard shows the deliberate cart-total bug, its failed assertion, and recorded browser evidence" width="100%">
|
|
44
|
+
</picture>
|
|
45
|
+
|
|
46
|
+
**See what memory contributed.** Inspect the knowledge supplied to a run and trace it back to the steps that used it.
|
|
47
|
+
|
|
48
|
+
<picture>
|
|
49
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-dark.png">
|
|
50
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-light.png">
|
|
51
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-light.png" alt="The Knowledge tab shows stored facts retrieved for the demo run" width="100%">
|
|
52
|
+
</picture>
|
|
53
|
+
|
|
54
|
+
**Run from your terminal.** Real output from the demo suite: checkout passes; the deliberate cart-total bug fails.
|
|
55
|
+
|
|
56
|
+
<picture>
|
|
57
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-dark.png">
|
|
58
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-light.png">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-light.png" alt="CLI output excerpt showing one passed test, one failed test, and eight of nine steps passing" width="100%">
|
|
60
|
+
</picture>
|
|
61
|
+
|
|
62
|
+
## Get started
|
|
63
|
+
|
|
64
|
+
Requires Node.js 24+.
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install -D agent-qa
|
|
68
|
+
npx agent-qa init
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Follow the [quickstart](https://vostride.com/docs/agent-qa/quickstart) to connect a model and run the demo, or [set up with your coding agent](https://vostride.com/docs/agent-qa/agent-quickstart).
|
|
72
|
+
|
|
73
|
+
> [!NOTE]
|
|
74
|
+
> Early stage. Expect bugs. [Report an issue](https://github.com/vostride/agent-qa/issues) when something breaks.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[Functional Source License (FSL-1.1-ALv2)](https://github.com/vostride/agent-qa/blob/main/LICENSE.md).
|
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
latestBuildOrdinal: () => latestBuildOrdinal,
|
|
56
56
|
loadCompletedRunHandoff: () => loadCompletedRunHandoff,
|
|
57
57
|
nextBuildOrdinal: () => nextBuildOrdinal,
|
|
58
|
+
purgeActionCache: () => purgeActionCache,
|
|
58
59
|
readBuildTagMap: () => readBuildTagMap,
|
|
59
60
|
recordBuildTag: () => recordBuildTag,
|
|
60
61
|
reserveBuildOrdinalForProducts: () => reserveBuildOrdinalForProducts,
|
|
@@ -146,39 +147,56 @@ function shouldWrite(policy) {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// src/fs-action-cache.ts
|
|
149
|
-
var CACHE_SCHEMA_VERSION =
|
|
150
|
+
var CACHE_SCHEMA_VERSION = 3;
|
|
151
|
+
var CACHE_ENTRY_FILE_RE = /^[0-9a-f]{16}\.json$/;
|
|
150
152
|
var FsActionCache = class {
|
|
151
153
|
root;
|
|
152
154
|
policy;
|
|
155
|
+
skipped = /* @__PURE__ */ new Set();
|
|
153
156
|
constructor(config) {
|
|
154
157
|
this.root = config.root;
|
|
155
158
|
this.policy = config.policy;
|
|
156
159
|
}
|
|
157
160
|
async get(key) {
|
|
158
|
-
if (this.policy.disableCache) return null;
|
|
161
|
+
if (this.policy.disableCache || this.skipped.has(key)) return null;
|
|
159
162
|
const onCurrent = await this.readEntry(this.policy.currentBranch, key);
|
|
160
|
-
if (onCurrent
|
|
163
|
+
if (onCurrent?.kind === "suppressed") return null;
|
|
164
|
+
if (onCurrent?.kind === "value") return onCurrent.value;
|
|
161
165
|
if (this.policy.currentBranch !== this.policy.defaultBranch) {
|
|
162
|
-
|
|
166
|
+
const onDefault = await this.readEntry(this.policy.defaultBranch, key);
|
|
167
|
+
return onDefault?.kind === "value" ? onDefault.value : null;
|
|
163
168
|
}
|
|
164
169
|
return null;
|
|
165
170
|
}
|
|
166
171
|
async set(key, value) {
|
|
167
172
|
if (!shouldWrite(this.policy)) return;
|
|
168
|
-
|
|
169
|
-
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
170
|
-
const envelope = { schemaVersion: CACHE_SCHEMA_VERSION, value };
|
|
171
|
-
await (0, import_promises.writeFile)(this.entryPath(dir, key), `${JSON.stringify(envelope, null, 2)}
|
|
172
|
-
`, "utf-8");
|
|
173
|
+
await this.writeEnvelope(key, { schemaVersion: CACHE_SCHEMA_VERSION, value });
|
|
173
174
|
}
|
|
174
175
|
async invalidate(key) {
|
|
175
|
-
|
|
176
|
+
if (!shouldWrite(this.policy)) {
|
|
177
|
+
this.skipped.add(key);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const { currentBranch, defaultBranch } = this.policy;
|
|
181
|
+
if (currentBranch !== defaultBranch) {
|
|
182
|
+
const onDefault = await this.readEntry(defaultBranch, key);
|
|
183
|
+
if (onDefault?.kind === "value") {
|
|
184
|
+
await this.writeEnvelope(key, { schemaVersion: CACHE_SCHEMA_VERSION, suppressed: true });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
176
188
|
try {
|
|
177
|
-
await (0, import_promises.unlink)(this.entryPath(
|
|
189
|
+
await (0, import_promises.unlink)(this.entryPath(this.branchDir(currentBranch), key));
|
|
178
190
|
} catch (err) {
|
|
179
|
-
if (err
|
|
191
|
+
if (!isMissing(err)) throw err;
|
|
180
192
|
}
|
|
181
193
|
}
|
|
194
|
+
async writeEnvelope(key, envelope) {
|
|
195
|
+
const dir = this.branchDir(this.policy.currentBranch);
|
|
196
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
197
|
+
await (0, import_promises.writeFile)(this.entryPath(dir, key), `${JSON.stringify(envelope, null, 2)}
|
|
198
|
+
`, "utf-8");
|
|
199
|
+
}
|
|
182
200
|
branchDir(branch) {
|
|
183
201
|
return (0, import_node_path2.join)(this.root, (0, import_agent_qa_schema.slugBranch)(branch));
|
|
184
202
|
}
|
|
@@ -190,7 +208,7 @@ var FsActionCache = class {
|
|
|
190
208
|
try {
|
|
191
209
|
raw = await (0, import_promises.readFile)(this.entryPath(this.branchDir(branch), key), "utf-8");
|
|
192
210
|
} catch (err) {
|
|
193
|
-
if (err
|
|
211
|
+
if (isMissing(err)) return null;
|
|
194
212
|
throw err;
|
|
195
213
|
}
|
|
196
214
|
let envelope;
|
|
@@ -200,9 +218,44 @@ var FsActionCache = class {
|
|
|
200
218
|
return null;
|
|
201
219
|
}
|
|
202
220
|
if (envelope?.schemaVersion !== CACHE_SCHEMA_VERSION) return null;
|
|
203
|
-
|
|
221
|
+
if (envelope.suppressed === true) return { kind: "suppressed" };
|
|
222
|
+
return envelope.value ? { kind: "value", value: envelope.value } : null;
|
|
204
223
|
}
|
|
205
224
|
};
|
|
225
|
+
function isMissing(err) {
|
|
226
|
+
return err.code === "ENOENT";
|
|
227
|
+
}
|
|
228
|
+
async function purgeActionCache(root) {
|
|
229
|
+
let branches;
|
|
230
|
+
try {
|
|
231
|
+
branches = await (0, import_promises.readdir)(root, { withFileTypes: true });
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (isMissing(err)) return 0;
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
let purged = 0;
|
|
237
|
+
for (const branch of branches) {
|
|
238
|
+
if (!branch.isDirectory() || (0, import_agent_qa_schema.slugBranch)(branch.name) !== branch.name) continue;
|
|
239
|
+
const dir = (0, import_node_path2.join)(root, branch.name);
|
|
240
|
+
const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
241
|
+
let removed = 0;
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
if (!entry.isFile() || !CACHE_ENTRY_FILE_RE.test(entry.name)) continue;
|
|
244
|
+
try {
|
|
245
|
+
await (0, import_promises.unlink)((0, import_node_path2.join)(dir, entry.name));
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (!isMissing(err)) throw err;
|
|
248
|
+
}
|
|
249
|
+
removed++;
|
|
250
|
+
}
|
|
251
|
+
purged += removed;
|
|
252
|
+
if (removed > 0 && removed === entries.length) {
|
|
253
|
+
await (0, import_promises.rmdir)(dir).catch(() => {
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return purged;
|
|
258
|
+
}
|
|
206
259
|
|
|
207
260
|
// src/storage-state-store.ts
|
|
208
261
|
var import_promises2 = require("fs/promises");
|
|
@@ -898,9 +951,11 @@ var import_node_os = require("os");
|
|
|
898
951
|
var import_agent_qa_runner = require("@vostride/agent-qa-runner");
|
|
899
952
|
var DEFAULT_DOCKER_BIN = "docker";
|
|
900
953
|
var DockerHookExecutor = class {
|
|
954
|
+
version;
|
|
901
955
|
dockerBin;
|
|
902
956
|
pullPolicy;
|
|
903
|
-
constructor(config
|
|
957
|
+
constructor(config) {
|
|
958
|
+
this.version = config.version;
|
|
904
959
|
this.dockerBin = config.dockerBin ?? DEFAULT_DOCKER_BIN;
|
|
905
960
|
this.pullPolicy = config.pullPolicy ?? "if-not-present";
|
|
906
961
|
}
|
|
@@ -908,15 +963,15 @@ var DockerHookExecutor = class {
|
|
|
908
963
|
const dockerBin = this.dockerBin;
|
|
909
964
|
const pullPolicy = this.pullPolicy;
|
|
910
965
|
const start = Date.now();
|
|
911
|
-
const image = import_agent_qa_runner.
|
|
912
|
-
if (pullPolicy === "always" || pullPolicy === "if-not-present") {
|
|
913
|
-
const needsPull = pullPolicy === "always" || !await imageExists(dockerBin, image);
|
|
914
|
-
if (needsPull) {
|
|
915
|
-
await pullImage(dockerBin, image);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
966
|
+
const image = (0, import_agent_qa_runner.hookRunnerImage)(hook.runtime, this.version);
|
|
918
967
|
let workDir;
|
|
919
968
|
try {
|
|
969
|
+
if (pullPolicy === "always" || pullPolicy === "if-not-present") {
|
|
970
|
+
const needsPull = pullPolicy === "always" || !await imageExists(dockerBin, image);
|
|
971
|
+
if (needsPull) {
|
|
972
|
+
await pullImage(dockerBin, image);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
920
975
|
workDir = await (0, import_promises3.mkdtemp)((0, import_node_path11.join)((0, import_node_os.tmpdir)(), "agent-qa-hook-"));
|
|
921
976
|
await (0, import_promises3.mkdir)((0, import_node_path11.join)(workDir, "tmp"), { recursive: true });
|
|
922
977
|
await (0, import_promises3.cp)(hook.file, (0, import_node_path11.join)(workDir, (0, import_node_path11.basename)(hook.file)));
|
|
@@ -1101,6 +1156,7 @@ function execDocker(dockerBin, args, timeout) {
|
|
|
1101
1156
|
latestBuildOrdinal,
|
|
1102
1157
|
loadCompletedRunHandoff,
|
|
1103
1158
|
nextBuildOrdinal,
|
|
1159
|
+
purgeActionCache,
|
|
1104
1160
|
readBuildTagMap,
|
|
1105
1161
|
recordBuildTag,
|
|
1106
1162
|
reserveBuildOrdinalForProducts,
|