@vostride/agent-qa-runner-local 0.2.3 → 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/dist/index.d.ts CHANGED
@@ -17,15 +17,9 @@ interface CachePolicy {
17
17
  /** `--disable-cache` — short-circuit both reads and writes. */
18
18
  disableCache: boolean;
19
19
  }
20
- /**
21
- * Writes are skipped when the current branch is protected (default ∪ configured)
22
- * unless `--save-cache` or CI; `--disable-cache` forbids all writes. Reads are not
23
- * governed here (D-14).
24
- */
25
20
  declare function shouldWrite(policy: CachePolicy): boolean;
26
21
 
27
- /** Envelopes live under <root>/<branchSlug>/<key>.json; any other stamp reads as a miss. */
28
- declare const CACHE_SCHEMA_VERSION = 2;
22
+ declare const CACHE_SCHEMA_VERSION = 3;
29
23
  interface FsActionCacheConfig {
30
24
  /** Cache root (e.g. DEFAULT_AGENT_QA_CACHE_DIR = '.agent-qa/cache'). */
31
25
  root: string;
@@ -35,14 +29,22 @@ interface FsActionCacheConfig {
35
29
  declare class FsActionCache implements ActionPlanCache {
36
30
  private readonly root;
37
31
  private readonly policy;
32
+ private readonly skipped;
38
33
  constructor(config: FsActionCacheConfig);
39
34
  get(key: CacheKey): Promise<CachedPlanValue | null>;
40
35
  set(key: CacheKey, value: CachedPlanValue): Promise<void>;
41
36
  invalidate(key: CacheKey): Promise<void>;
37
+ private writeEnvelope;
42
38
  private branchDir;
43
39
  private entryPath;
44
40
  private readEntry;
45
41
  }
42
+ /**
43
+ * Removes every `<root>/<branch-slug>/<16-hex>.json` entry, and a branch directory only when that
44
+ * leaves it empty. Nothing else under `root` is touched, so a cache root that also holds other
45
+ * files keeps them. Returns the number of entries removed.
46
+ */
47
+ declare function purgeActionCache(root: string): Promise<number>;
46
48
 
47
49
  /** Subdirectory under the runtime root holding the raw storage-state blobs. */
48
50
  declare const STORAGE_STATES_SUBDIR = "storage-states";
@@ -113,11 +115,6 @@ declare class FsHandoffStore implements HandoffStorePort {
113
115
  constructor(config: FsHandoffStoreConfig);
114
116
  save(handoff: CompletedRunHandoff): void;
115
117
  }
116
- /**
117
- * The fs read leg of the handoff version discipline (D-19): read the single document, parse it,
118
- * and delegate to the schema package's `guardCompletedRunHandoff`. A newer-than-known schemaVersion
119
- * surfaces the schema's typed `HandoffVersionError` — this leg re-implements no version rule.
120
- */
121
118
  declare function loadCompletedRunHandoff(filePath: string): CompletedRunHandoff;
122
119
 
123
120
  /** The single JSON document each run writes its run artifact to. */
@@ -182,12 +179,6 @@ interface CompressResult {
182
179
  imageWidth: number;
183
180
  imageHeight: number;
184
181
  }
185
- /**
186
- * The runner `ScreenshotCompressor` port impl (D-08 / Q10). `compress` covers the LLM-planner
187
- * compression call site; `dimensions` covers the action-space-width metadata fallback done inline
188
- * via `sharp(screenshot).metadata()` at v1 loop.ts. The loop applies `stampCaptureMode` itself
189
- * (runner-owned since 352), so this stays purely pixel-level and returns v1's CompressResult shape.
190
- */
191
182
  declare class SharpScreenshotCompressor implements ScreenshotCompressor {
192
183
  private readonly logger?;
193
184
  constructor(logger?: ScreenshotCompressorLogger | undefined);
@@ -212,8 +203,6 @@ interface ImageDimensions {
212
203
  width: number;
213
204
  height: number;
214
205
  }
215
- /** Read image buffer width/height without decoding pixels. Used by mobile adapters for
216
- * action-space drift assertions (D-06/D-07). */
217
206
  declare function getImageDimensions(buffer: Buffer): Promise<ImageDimensions | undefined>;
218
207
  declare function flattenOntoWhite(buffer: Buffer): Promise<Buffer>;
219
208
 
@@ -288,24 +277,18 @@ declare function readBuildTagMap(opts: {
288
277
  }): BuildTagMap;
289
278
 
290
279
  interface DockerHookExecutorConfig {
280
+ version: string;
291
281
  dockerBin?: string;
292
282
  pullPolicy?: 'always' | 'if-not-present' | 'never';
293
283
  }
294
- /**
295
- * The D-06 hook-execution impl. Constructed once at the CLI composition root and threaded
296
- * into the runner `runHooks` orchestration behind the {@link HookExecutorPort}. A per-hook
297
- * timeout resolves a FAILED {@link HookResult} (exit-124-style error), never a throw.
298
- */
299
284
  declare class DockerHookExecutor implements HookExecutorPort {
285
+ private readonly version;
300
286
  private readonly dockerBin;
301
287
  private readonly pullPolicy;
302
- constructor(config?: DockerHookExecutorConfig);
288
+ constructor(config: DockerHookExecutorConfig);
303
289
  execute(hook: HookDefinition, opts: HookExecuteOptions): Promise<HookResult>;
304
290
  }
305
- /**
306
- * Docker availability probe (re-homed from core/src/hooks/sandbox-runner.ts). The CLI
307
- * `doctor` command consumes this to report whether the hook sandbox can run.
308
- */
291
+ /** Docker availability probe; `doctor` reports from it whether the hook sandbox can run. */
309
292
  declare function checkDockerAvailable(dockerBin?: string): Promise<boolean>;
310
293
 
311
- export { ARTIFACT_FILE, type AlignOptions, BUILD_ORDINALS_FILE, BUILD_TAGS_FILE, type BranchResolverOptions, type BuildOrdinalReservation, BuildOrdinalReservationError, type BuildOrdinalReservationErrorCode, type BuildTagMap, CACHE_SCHEMA_VERSION, type CachePolicy, type CompressOptions, type CompressResult, DockerHookExecutor, type DockerHookExecutorConfig, EVENTS_FILE, FsActionCache, type FsActionCacheConfig, FsEventLog, type FsEventLogConfig, FsEvidenceArtifactStore, type FsEvidenceArtifactStoreConfig, FsHandoffStore, type FsHandoffStoreConfig, FsRunArtifactStore, type FsRunArtifactStoreConfig, FsStorageStateStore, type FsStorageStateStoreConfig, HANDOFF_FILE, type ImageDimensions, type NextBuildOrdinalOptions, RUNS_SUBDIR, RUN_EVIDENCE_BLOBS_SUBDIR, type ReserveBuildOrdinalForProductsOptions, type ReserveBuildOrdinalForProductsResult, type ResolvedBranch, STORAGE_STATES_SUBDIR, type ScreenshotCompressorLogger, SharpScreenshotCompressor, alignToWindow, checkDockerAvailable, flattenOntoWhite, getImageDimensions, latestBuildOrdinal, loadCompletedRunHandoff, nextBuildOrdinal, readBuildTagMap, recordBuildTag, reserveBuildOrdinalForProducts, resolveBranch, resolveBuildTag, shouldWrite };
294
+ export { ARTIFACT_FILE, type AlignOptions, BUILD_ORDINALS_FILE, BUILD_TAGS_FILE, type BranchResolverOptions, type BuildOrdinalReservation, BuildOrdinalReservationError, type BuildOrdinalReservationErrorCode, type BuildTagMap, CACHE_SCHEMA_VERSION, type CachePolicy, type CompressOptions, type CompressResult, DockerHookExecutor, type DockerHookExecutorConfig, EVENTS_FILE, FsActionCache, type FsActionCacheConfig, FsEventLog, type FsEventLogConfig, FsEvidenceArtifactStore, type FsEvidenceArtifactStoreConfig, FsHandoffStore, type FsHandoffStoreConfig, FsRunArtifactStore, type FsRunArtifactStoreConfig, FsStorageStateStore, type FsStorageStateStoreConfig, HANDOFF_FILE, type ImageDimensions, type NextBuildOrdinalOptions, RUNS_SUBDIR, RUN_EVIDENCE_BLOBS_SUBDIR, type ReserveBuildOrdinalForProductsOptions, type ReserveBuildOrdinalForProductsResult, type ResolvedBranch, STORAGE_STATES_SUBDIR, type ScreenshotCompressorLogger, SharpScreenshotCompressor, alignToWindow, checkDockerAvailable, flattenOntoWhite, getImageDimensions, latestBuildOrdinal, loadCompletedRunHandoff, nextBuildOrdinal, purgeActionCache, readBuildTagMap, recordBuildTag, reserveBuildOrdinalForProducts, resolveBranch, resolveBuildTag, shouldWrite };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/fs-action-cache.ts
2
- import { mkdir, readFile, unlink, writeFile } from "fs/promises";
2
+ import { mkdir, readdir, readFile, rmdir, unlink, writeFile } from "fs/promises";
3
3
  import { join as join2 } from "path";
4
4
 
5
5
  // src/branch-resolver.ts
@@ -79,39 +79,56 @@ function shouldWrite(policy) {
79
79
  }
80
80
 
81
81
  // src/fs-action-cache.ts
82
- var CACHE_SCHEMA_VERSION = 2;
82
+ var CACHE_SCHEMA_VERSION = 3;
83
+ var CACHE_ENTRY_FILE_RE = /^[0-9a-f]{16}\.json$/;
83
84
  var FsActionCache = class {
84
85
  root;
85
86
  policy;
87
+ skipped = /* @__PURE__ */ new Set();
86
88
  constructor(config) {
87
89
  this.root = config.root;
88
90
  this.policy = config.policy;
89
91
  }
90
92
  async get(key) {
91
- if (this.policy.disableCache) return null;
93
+ if (this.policy.disableCache || this.skipped.has(key)) return null;
92
94
  const onCurrent = await this.readEntry(this.policy.currentBranch, key);
93
- if (onCurrent !== null) return onCurrent;
95
+ if (onCurrent?.kind === "suppressed") return null;
96
+ if (onCurrent?.kind === "value") return onCurrent.value;
94
97
  if (this.policy.currentBranch !== this.policy.defaultBranch) {
95
- return this.readEntry(this.policy.defaultBranch, key);
98
+ const onDefault = await this.readEntry(this.policy.defaultBranch, key);
99
+ return onDefault?.kind === "value" ? onDefault.value : null;
96
100
  }
97
101
  return null;
98
102
  }
99
103
  async set(key, value) {
100
104
  if (!shouldWrite(this.policy)) return;
101
- const dir = this.branchDir(this.policy.currentBranch);
102
- await mkdir(dir, { recursive: true });
103
- const envelope = { schemaVersion: CACHE_SCHEMA_VERSION, value };
104
- await writeFile(this.entryPath(dir, key), `${JSON.stringify(envelope, null, 2)}
105
- `, "utf-8");
105
+ await this.writeEnvelope(key, { schemaVersion: CACHE_SCHEMA_VERSION, value });
106
106
  }
107
107
  async invalidate(key) {
108
- const dir = this.branchDir(this.policy.currentBranch);
108
+ if (!shouldWrite(this.policy)) {
109
+ this.skipped.add(key);
110
+ return;
111
+ }
112
+ const { currentBranch, defaultBranch } = this.policy;
113
+ if (currentBranch !== defaultBranch) {
114
+ const onDefault = await this.readEntry(defaultBranch, key);
115
+ if (onDefault?.kind === "value") {
116
+ await this.writeEnvelope(key, { schemaVersion: CACHE_SCHEMA_VERSION, suppressed: true });
117
+ return;
118
+ }
119
+ }
109
120
  try {
110
- await unlink(this.entryPath(dir, key));
121
+ await unlink(this.entryPath(this.branchDir(currentBranch), key));
111
122
  } catch (err) {
112
- if (err.code !== "ENOENT") throw err;
123
+ if (!isMissing(err)) throw err;
113
124
  }
114
125
  }
126
+ async writeEnvelope(key, envelope) {
127
+ const dir = this.branchDir(this.policy.currentBranch);
128
+ await mkdir(dir, { recursive: true });
129
+ await writeFile(this.entryPath(dir, key), `${JSON.stringify(envelope, null, 2)}
130
+ `, "utf-8");
131
+ }
115
132
  branchDir(branch) {
116
133
  return join2(this.root, slugBranch(branch));
117
134
  }
@@ -123,7 +140,7 @@ var FsActionCache = class {
123
140
  try {
124
141
  raw = await readFile(this.entryPath(this.branchDir(branch), key), "utf-8");
125
142
  } catch (err) {
126
- if (err.code === "ENOENT") return null;
143
+ if (isMissing(err)) return null;
127
144
  throw err;
128
145
  }
129
146
  let envelope;
@@ -133,9 +150,44 @@ var FsActionCache = class {
133
150
  return null;
134
151
  }
135
152
  if (envelope?.schemaVersion !== CACHE_SCHEMA_VERSION) return null;
136
- return envelope.value ?? null;
153
+ if (envelope.suppressed === true) return { kind: "suppressed" };
154
+ return envelope.value ? { kind: "value", value: envelope.value } : null;
137
155
  }
138
156
  };
157
+ function isMissing(err) {
158
+ return err.code === "ENOENT";
159
+ }
160
+ async function purgeActionCache(root) {
161
+ let branches;
162
+ try {
163
+ branches = await readdir(root, { withFileTypes: true });
164
+ } catch (err) {
165
+ if (isMissing(err)) return 0;
166
+ throw err;
167
+ }
168
+ let purged = 0;
169
+ for (const branch of branches) {
170
+ if (!branch.isDirectory() || slugBranch(branch.name) !== branch.name) continue;
171
+ const dir = join2(root, branch.name);
172
+ const entries = await readdir(dir, { withFileTypes: true });
173
+ let removed = 0;
174
+ for (const entry of entries) {
175
+ if (!entry.isFile() || !CACHE_ENTRY_FILE_RE.test(entry.name)) continue;
176
+ try {
177
+ await unlink(join2(dir, entry.name));
178
+ } catch (err) {
179
+ if (!isMissing(err)) throw err;
180
+ }
181
+ removed++;
182
+ }
183
+ purged += removed;
184
+ if (removed > 0 && removed === entries.length) {
185
+ await rmdir(dir).catch(() => {
186
+ });
187
+ }
188
+ }
189
+ return purged;
190
+ }
139
191
 
140
192
  // src/storage-state-store.ts
141
193
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
@@ -849,7 +901,7 @@ import { readFile as readFile3, mkdtemp, mkdir as mkdir3, rm, cp } from "fs/prom
849
901
  import { join as join10, basename } from "path";
850
902
  import { tmpdir } from "os";
851
903
  import {
852
- RUNTIME_IMAGE_MAP,
904
+ hookRunnerImage,
853
905
  parseEnvFile,
854
906
  AUTH_STATE_HOOK_CONTAINER_STORAGE_STATE_PATH,
855
907
  AUTH_STATE_HOOK_STORAGE_STATE_FILENAME,
@@ -860,9 +912,11 @@ import {
860
912
  } from "@vostride/agent-qa-runner";
861
913
  var DEFAULT_DOCKER_BIN = "docker";
862
914
  var DockerHookExecutor = class {
915
+ version;
863
916
  dockerBin;
864
917
  pullPolicy;
865
- constructor(config = {}) {
918
+ constructor(config) {
919
+ this.version = config.version;
866
920
  this.dockerBin = config.dockerBin ?? DEFAULT_DOCKER_BIN;
867
921
  this.pullPolicy = config.pullPolicy ?? "if-not-present";
868
922
  }
@@ -870,15 +924,15 @@ var DockerHookExecutor = class {
870
924
  const dockerBin = this.dockerBin;
871
925
  const pullPolicy = this.pullPolicy;
872
926
  const start = Date.now();
873
- const image = RUNTIME_IMAGE_MAP[hook.runtime];
874
- if (pullPolicy === "always" || pullPolicy === "if-not-present") {
875
- const needsPull = pullPolicy === "always" || !await imageExists(dockerBin, image);
876
- if (needsPull) {
877
- await pullImage(dockerBin, image);
878
- }
879
- }
927
+ const image = hookRunnerImage(hook.runtime, this.version);
880
928
  let workDir;
881
929
  try {
930
+ if (pullPolicy === "always" || pullPolicy === "if-not-present") {
931
+ const needsPull = pullPolicy === "always" || !await imageExists(dockerBin, image);
932
+ if (needsPull) {
933
+ await pullImage(dockerBin, image);
934
+ }
935
+ }
882
936
  workDir = await mkdtemp(join10(tmpdir(), "agent-qa-hook-"));
883
937
  await mkdir3(join10(workDir, "tmp"), { recursive: true });
884
938
  await cp(hook.file, join10(workDir, basename(hook.file)));
@@ -1062,6 +1116,7 @@ export {
1062
1116
  latestBuildOrdinal,
1063
1117
  loadCompletedRunHandoff,
1064
1118
  nextBuildOrdinal,
1119
+ purgeActionCache,
1065
1120
  readBuildTagMap,
1066
1121
  recordBuildTag,
1067
1122
  reserveBuildOrdinalForProducts,