From 7d31f4654010d2595e7862dcf47d1c89e5a803d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Tue, 23 Jun 2026 13:03:32 +0200 Subject: [PATCH] Add artifact kind metadata --- .../argent-cli/test/run-artifacts.test.ts | 2 + packages/argent-mcp/test/content.test.ts | 13 +++++- packages/argent-tools-client/src/artifacts.ts | 15 ++++++ packages/argent-tools-client/src/index.ts | 1 + .../test/artifacts.test.ts | 17 ++++++- packages/registry/src/artifacts.ts | 44 ++++++++++++++++-- packages/registry/src/index.ts | 1 + packages/registry/src/types.ts | 7 +-- packages/tool-server/src/artifacts.ts | 1 + .../native-profiler-analyze.ts | 5 +- .../native-profiler/native-profiler-stop.ts | 18 ++++++-- .../profiler/react/react-profiler-analyze.ts | 13 +++--- .../src/tools/screenshot-diff/index.ts | 12 ++++- .../tool-server/src/tools/screenshot/index.ts | 24 ++++++++-- .../test/android-perfetto/dispatch.test.ts | 1 + packages/tool-server/test/artifacts.test.ts | 46 +++++++++++++++---- .../ios-instruments/stop-recovery.test.ts | 13 +++++- .../test/screenshot-diff-tool.test.ts | 2 + .../tool-server/test/screenshot-tool.test.ts | 1 + 19 files changed, 198 insertions(+), 38 deletions(-) diff --git a/packages/argent-cli/test/run-artifacts.test.ts b/packages/argent-cli/test/run-artifacts.test.ts index f11aebdd5..1bd78da82 100644 --- a/packages/argent-cli/test/run-artifacts.test.ts +++ b/packages/argent-cli/test/run-artifacts.test.ts @@ -129,6 +129,7 @@ describe("CLI run — artifact materialization end-to-end", () => { return { [ARTIFACT_MARKER]: true, id: "loc-1", + kind: "screenshot", filename: "shot.png", mimeType: "image/png", size: st.size, @@ -159,6 +160,7 @@ describe("CLI run — artifact materialization end-to-end", () => { image: { [ARTIFACT_MARKER]: true, id: "rem-1", + kind: "screenshot", filename: "shot.png", mimeType: "image/png", size: PNG.length, diff --git a/packages/argent-mcp/test/content.test.ts b/packages/argent-mcp/test/content.test.ts index 25c8d158a..266029659 100644 --- a/packages/argent-mcp/test/content.test.ts +++ b/packages/argent-mcp/test/content.test.ts @@ -12,12 +12,21 @@ import { flowRunToMcpContent, type FlowExecuteResult, } from "../src/content.js"; -import { ARTIFACT_MARKER, type ArtifactHandle } from "@argent/tools-client"; +import { ARTIFACT_MARKER, type ArtifactHandle, type ArtifactKind } from "@argent/tools-client"; const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; +function artifactKind(filename: string, mimeType: string): ArtifactKind { + if (mimeType.startsWith("image/")) return "screenshot"; + if (filename.includes("cpu")) return "native-profile-cpu"; + if (filename.includes("hangs")) return "native-profile-hangs"; + if (filename.includes("leaks")) return "native-profile-leaks"; + return "native-profile-trace"; +} + function artifactHandle(id: string, filename: string, mimeType: string): ArtifactHandle { - return { [ARTIFACT_MARKER]: true, id, filename, mimeType, size: 0 }; + const kind = artifactKind(filename, mimeType); + return { [ARTIFACT_MARKER]: true, id, kind, filename, mimeType, size: 0 }; } function fetchReturning(bytes: number[]): typeof fetch { diff --git a/packages/argent-tools-client/src/artifacts.ts b/packages/argent-tools-client/src/artifacts.ts index 2767de70b..157800ade 100644 --- a/packages/argent-tools-client/src/artifacts.ts +++ b/packages/argent-tools-client/src/artifacts.ts @@ -39,9 +39,24 @@ const execFileAsync = promisify(execFile); /** Must match the tool-server's wire contract (`tool-server/src/artifacts.ts`). */ export const ARTIFACT_MARKER = "__argentArtifact" as const; +export type ArtifactKind = + | "screenshot" + | "screenshot-diff" + | "screenshot-diff-context" + | "native-profile-trace" + | "native-profile-cpu" + | "native-profile-hangs" + | "native-profile-leaks" + | "native-profile-report" + | "react-profile-cpu" + | "react-profile-commits" + | "react-profile-report"; + export interface ArtifactHandle { [ARTIFACT_MARKER]: true; id: string; + /** Semantic artifact category, distinct from MIME type. */ + kind: ArtifactKind; filename: string; mimeType: string; size: number; diff --git a/packages/argent-tools-client/src/index.ts b/packages/argent-tools-client/src/index.ts index d9d542de3..37d5e4caa 100644 --- a/packages/argent-tools-client/src/index.ts +++ b/packages/argent-tools-client/src/index.ts @@ -54,6 +54,7 @@ export { artifactsRoot, ARTIFACT_MARKER, type ArtifactHandle, + type ArtifactKind, type MaterializeContext, type MaterializedImage, } from "./artifacts.js"; diff --git a/packages/argent-tools-client/test/artifacts.test.ts b/packages/argent-tools-client/test/artifacts.test.ts index 4e9d9a871..3b3c1de3d 100644 --- a/packages/argent-tools-client/test/artifacts.test.ts +++ b/packages/argent-tools-client/test/artifacts.test.ts @@ -9,11 +9,21 @@ import { getDeviceIdFromArgs, artifactDir, ARTIFACT_MARKER, + type ArtifactKind, type ArtifactHandle, } from "../src/artifacts.js"; +function artifactKind(filename: string, mimeType: string): ArtifactKind { + if (mimeType.startsWith("image/")) return "screenshot"; + if (filename.includes("cpu")) return "native-profile-cpu"; + if (filename.includes("hangs")) return "native-profile-hangs"; + if (filename.includes("leaks")) return "native-profile-leaks"; + return "native-profile-trace"; +} + function handle(id: string, filename: string, mimeType: string): ArtifactHandle { - return { [ARTIFACT_MARKER]: true, id, filename, mimeType, size: 0 }; + const kind = artifactKind(filename, mimeType); + return { [ARTIFACT_MARKER]: true, id, kind, filename, mimeType, size: 0 }; } const PNG = [0x89, 0x50, 0x4e, 0x47, 0x01, 0x02]; @@ -141,6 +151,7 @@ describe("materializeArtifacts", () => { const h: ArtifactHandle = { [ARTIFACT_MARKER]: true, id: "trunc", + kind: "native-profile-cpu", filename: "data.xml", mimeType: "application/xml", size: 99, // server announced 99 bytes… @@ -190,6 +201,7 @@ describe("materializeArtifacts local short-circuit", () => { return { [ARTIFACT_MARKER]: true, id, + kind: artifactKind(filename, mimeType), filename, mimeType, size: st.size, @@ -237,6 +249,7 @@ describe("materializeArtifacts local short-circuit", () => { const h: ArtifactHandle = { [ARTIFACT_MARKER]: true, id: "img2", + kind: "screenshot", filename: "shot.png", mimeType: "image/png", size: PNG.length, @@ -258,6 +271,7 @@ describe("materializeArtifacts local short-circuit", () => { const h: ArtifactHandle = { [ARTIFACT_MARKER]: true, id: "img3", + kind: "screenshot", filename: "shot.png", mimeType: "image/png", size: PNG.length, @@ -320,6 +334,7 @@ describe("materializeArtifacts directory bundles", () => { return { [ARTIFACT_MARKER]: true, id, + kind: "native-profile-trace", filename: "session.trace", mimeType: "application/octet-stream", size: 0, diff --git a/packages/registry/src/artifacts.ts b/packages/registry/src/artifacts.ts index a347b2916..830d5fb57 100644 --- a/packages/registry/src/artifacts.ts +++ b/packages/registry/src/artifacts.ts @@ -29,10 +29,28 @@ import { basename, extname } from "node:path"; /** Discriminant key identifying an artifact handle inside a tool result. */ export const ARTIFACT_MARKER = "__argentArtifact" as const; +/** + * Semantic artifact category. MIME type tells consumers how to read the bytes; + * kind tells them what the artifact represents. + */ +export type ArtifactKind = + | "screenshot" + | "screenshot-diff" + | "screenshot-diff-context" + | "native-profile-trace" + | "native-profile-cpu" + | "native-profile-hangs" + | "native-profile-leaks" + | "native-profile-report" + | "react-profile-cpu" + | "react-profile-commits" + | "react-profile-report"; + /** Wire contract: what a tool returns in place of a host path. */ export interface ArtifactHandle { [ARTIFACT_MARKER]: true; id: string; + kind: ArtifactKind; filename: string; mimeType: string; size: number; @@ -59,6 +77,7 @@ export interface ArtifactHandle { /** Internal entry the HTTP route reads to stream a registered artifact. */ export interface ArtifactEntry { path: string; + kind: ArtifactKind; filename: string; mimeType: string; size: number; @@ -68,6 +87,7 @@ export interface ArtifactEntry { /** Public metadata returned by the artifact inventory endpoint. */ export interface ArtifactListItem { id: string; + kind: ArtifactKind; filename: string; mimeType: string; size: number; @@ -92,6 +112,10 @@ function inferMimeType(filePath: string): string { } export interface RegisterArtifactOptions { + /** Absolute path of the file or directory on the tool-server host. */ + hostPath: string; + /** Semantic category of the artifact, distinct from its MIME type. */ + kind: ArtifactKind; /** Override the basename presented to the client. Defaults to the host basename. */ filename?: string; /** Override the inferred MIME type. */ @@ -112,12 +136,13 @@ export interface RegisterArtifactOptions { export class ArtifactStore { private readonly entries = new Map(); - async register(hostPath: string, opts?: RegisterArtifactOptions): Promise { - const filename = opts?.filename ?? basename(hostPath); - const mimeType = opts?.mimeType ?? inferMimeType(hostPath); + async register(opts: RegisterArtifactOptions): Promise { + const { hostPath } = opts; + const filename = opts.filename ?? basename(hostPath); + const mimeType = opts.mimeType ?? inferMimeType(hostPath); let size = 0; let mtimeMs: number | undefined; - let isDirectory = opts?.archive === "tar.gz"; + let isDirectory = opts.archive === "tar.gz"; try { const st = await stat(hostPath); size = st.size; @@ -129,10 +154,18 @@ export class ArtifactStore { // they don't match what's on disk at read time. } const id = randomUUID(); - this.entries.set(id, { path: hostPath, filename, mimeType, size, isDirectory }); + this.entries.set(id, { + path: hostPath, + kind: opts.kind, + filename, + mimeType, + size, + isDirectory, + }); const handle: ArtifactHandle = { [ARTIFACT_MARKER]: true, id, + kind: opts.kind, filename, mimeType, size, @@ -150,6 +183,7 @@ export class ArtifactStore { list(): ArtifactListItem[] { return [...this.entries.entries()].map(([id, entry]) => ({ id, + kind: entry.kind, filename: entry.filename, mimeType: entry.mimeType, size: entry.size, diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 140591c0c..9cdf64132 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -23,6 +23,7 @@ export type { ArtifactHandle, ArtifactEntry, ArtifactListItem, + ArtifactKind, RegisterArtifactOptions, } from "./artifacts"; export { diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index a60583cc1..fff98d58f 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -124,9 +124,10 @@ export interface InvokeToolOptions { * this for every invocation: it carries the caller's {@link InvokeToolOptions} * (e.g. `signal`) plus cross-cutting context the registry owns — currently the * {@link ArtifactStore}, so any tool that produces a host file can register it - * (`ctx.artifacts.register(path)`) without declaring a per-tool service. The - * registry always populates `artifacts`; it is only ever absent when `execute` - * is called directly (bypassing `invokeTool`), e.g. in a unit test. + * (`ctx.artifacts.register({ hostPath, kind })`) without declaring a per-tool + * service. The registry always populates `artifacts`; it is only ever absent + * when `execute` is called directly (bypassing `invokeTool`), e.g. in a unit + * test. */ export interface ToolContext extends InvokeToolOptions { artifacts: ArtifactStore; diff --git a/packages/tool-server/src/artifacts.ts b/packages/tool-server/src/artifacts.ts index 12e9667c8..08e786c7a 100644 --- a/packages/tool-server/src/artifacts.ts +++ b/packages/tool-server/src/artifacts.ts @@ -31,6 +31,7 @@ export { type ArtifactHandle, type ArtifactEntry, type ArtifactListItem, + type ArtifactKind, type RegisterArtifactOptions, } from "@argent/registry"; diff --git a/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-analyze.ts b/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-analyze.ts index 182fe1420..8f39420a8 100644 --- a/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-analyze.ts +++ b/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-analyze.ts @@ -69,7 +69,10 @@ Fails if native-profiler-stop has not been called first to export trace data.`, return { ...result, reportFile: result.reportFile - ? await requireArtifacts(ctx).register(result.reportFile) + ? await requireArtifacts(ctx).register({ + hostPath: result.reportFile, + kind: "native-profile-report", + }) : null, }; }, diff --git a/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-stop.ts b/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-stop.ts index 6d7d87cdd..730f6b36b 100644 --- a/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-stop.ts +++ b/packages/tool-server/src/tools/profiler/native-profiler/native-profiler-stop.ts @@ -11,7 +11,7 @@ import { stopNativeProfilerIos, type IosStopResult } from "./platforms/ios"; import { stopNativeProfilerAndroid, type AndroidStopResult } from "./platforms/android"; import type { ExportDiagnostics } from "../../../utils/ios-profiler/export"; import { requireArtifacts, type ArtifactHandle } from "../../../artifacts"; -import type { ArtifactStore } from "@argent/registry"; +import type { ArtifactKind, ArtifactStore } from "@argent/registry"; const zodSchema = z.object({ device_id: z @@ -55,6 +55,13 @@ const capability = { android: { emulator: true, device: true, unknown: true }, } as const; +const EXPORTED_FILE_KIND_BY_KEY: Record = { + cpu: "native-profile-cpu", + hangs: "native-profile-hangs", + leaks: "native-profile-leaks", + pftrace: "native-profile-trace", +}; + /** Register each non-null exported file path as a downloadable artifact. */ async function exportedFilesToArtifacts( store: ArtifactStore, @@ -62,7 +69,12 @@ async function exportedFilesToArtifacts( ): Promise> { const out: Record = {}; for (const [key, filePath] of Object.entries(files)) { - out[key] = filePath ? await store.register(filePath) : null; + out[key] = filePath + ? await store.register({ + hostPath: filePath, + kind: EXPORTED_FILE_KIND_BY_KEY[key] ?? "native-profile-trace", + }) + : null; } return out; } @@ -73,7 +85,7 @@ async function exportedFilesToArtifacts( * be stat'd at registration (e.g. a recovered session). */ function registerTrace(store: ArtifactStore, traceFile: string): Promise { - return store.register(traceFile, { archive: "tar.gz" }); + return store.register({ hostPath: traceFile, kind: "native-profile-trace", archive: "tar.gz" }); } export const nativeProfilerStopTool: ToolDefinition, StopResult> = { diff --git a/packages/tool-server/src/tools/profiler/react/react-profiler-analyze.ts b/packages/tool-server/src/tools/profiler/react/react-profiler-analyze.ts index 703e173a2..00cc33586 100644 --- a/packages/tool-server/src/tools/profiler/react/react-profiler-analyze.ts +++ b/packages/tool-server/src/tools/profiler/react/react-profiler-analyze.ts @@ -21,7 +21,7 @@ import { } from "../../../utils/react-profiler/debug/dump"; import { serializeCpuSampleIndex } from "../../../utils/react-profiler/pipeline/00-cpu-correlate"; import { requireArtifacts, type ArtifactHandle } from "../../../artifacts"; -import type { ArtifactStore } from "@argent/registry"; +import type { ArtifactKind, ArtifactStore } from "@argent/registry"; /** * Register a server-side file path as a downloadable artifact (or pass through @@ -32,9 +32,10 @@ import type { ArtifactStore } from "@argent/registry"; */ async function fileArtifact( store: ArtifactStore, - p: string | null | undefined + p: string | null | undefined, + kind: ArtifactKind ): Promise { - return p ? store.register(p) : null; + return p ? store.register({ hostPath: p, kind }) : null; } const annotationSchema = z.object({ @@ -227,13 +228,13 @@ Fails if react-profiler-stop has not been called or no profiling data is stored. const artifacts = requireArtifacts(ctx); const result: Record = { report, - reportFile: await fileArtifact(artifacts, reportFile), + reportFile: await fileArtifact(artifacts, reportFile, "react-profile-report"), hotCommitsTotal, hotCommitsShown, sessionFiles: { sessionId: sessionPaths.sessionId, - cpuProfile: await fileArtifact(artifacts, sessionPaths.cpuProfilePath), - commits: await fileArtifact(artifacts, sessionPaths.commitsPath), + cpuProfile: await fileArtifact(artifacts, sessionPaths.cpuProfilePath, "react-profile-cpu"), + commits: await fileArtifact(artifacts, sessionPaths.commitsPath, "react-profile-commits"), }, }; diff --git a/packages/tool-server/src/tools/screenshot-diff/index.ts b/packages/tool-server/src/tools/screenshot-diff/index.ts index 974cbaa5f..d0e31ea9b 100644 --- a/packages/tool-server/src/tools/screenshot-diff/index.ts +++ b/packages/tool-server/src/tools/screenshot-diff/index.ts @@ -147,11 +147,19 @@ export async function executeScreenshotDiffTool( return { summary: result.summary, ...(result.diffPath - ? { diffPath: await artifacts.register(result.diffPath, { mimeType: "image/png" }) } + ? { + diffPath: await artifacts.register({ + hostPath: result.diffPath, + kind: "screenshot-diff", + mimeType: "image/png", + }), + } : {}), ...(result.contextDiffPath ? { - contextDiffPath: await artifacts.register(result.contextDiffPath, { + contextDiffPath: await artifacts.register({ + hostPath: result.contextDiffPath, + kind: "screenshot-diff-context", mimeType: "image/png", }), } diff --git a/packages/tool-server/src/tools/screenshot/index.ts b/packages/tool-server/src/tools/screenshot/index.ts index f95e92391..d07d1a2c2 100644 --- a/packages/tool-server/src/tools/screenshot/index.ts +++ b/packages/tool-server/src/tools/screenshot/index.ts @@ -146,7 +146,11 @@ Fails if the simulator-server / emulator backend / Chromium CDP is not reachable scale: params.scale, downscaler: params.downscaler, }); - const image = await requireArtifacts(ctx).register(capturedPath, { mimeType: "image/png" }); + const image = await requireArtifacts(ctx).register({ + hostPath: capturedPath, + kind: "screenshot", + mimeType: "image/png", + }); return { image }; } @@ -154,7 +158,11 @@ Fails if the simulator-server / emulator backend / Chromium CDP is not reachable // tvOS has no simulator-server backend, so capture via xcrun instead. if (device.platform === "ios" && (await isTvOsSimulator(params.udid))) { const pngPath = await tvScreenshot(params.udid, scale, signal); - const image = await requireArtifacts(ctx).register(pngPath, { mimeType: "image/png" }); + const image = await requireArtifacts(ctx).register({ + hostPath: pngPath, + kind: "screenshot", + mimeType: "image/png", + }); return { image }; } @@ -163,7 +171,11 @@ Fails if the simulator-server / emulator backend / Chromium CDP is not reachable // Vega device would throw), so capture directly here. if (device.platform === "vega") { const pngPath = await captureVegaScreenshotPng({ scale: params.scale }); - const image = await requireArtifacts(ctx).register(pngPath, { mimeType: "image/png" }); + const image = await requireArtifacts(ctx).register({ + hostPath: pngPath, + kind: "screenshot", + mimeType: "image/png", + }); return { image }; } @@ -175,7 +187,11 @@ Fails if the simulator-server / emulator backend / Chromium CDP is not reachable signal, params.scale ); - const image = await requireArtifacts(ctx).register(capturedPath, { mimeType: "image/png" }); + const image = await requireArtifacts(ctx).register({ + hostPath: capturedPath, + kind: "screenshot", + mimeType: "image/png", + }); return { image }; }, }; diff --git a/packages/tool-server/test/android-perfetto/dispatch.test.ts b/packages/tool-server/test/android-perfetto/dispatch.test.ts index 7a474a0a7..b023eb551 100644 --- a/packages/tool-server/test/android-perfetto/dispatch.test.ts +++ b/packages/tool-server/test/android-perfetto/dispatch.test.ts @@ -102,6 +102,7 @@ describe("native-profiler-* dispatch by session platform", () => { // the filename still proves it routed through the Android (.pftrace) path. expect(stop.traceFile).toMatchObject({ __argentArtifact: true, + kind: "native-profile-trace", archive: "tar.gz", filename: "android.pftrace", }); diff --git a/packages/tool-server/test/artifacts.test.ts b/packages/tool-server/test/artifacts.test.ts index c8d4dae7a..8e8117b49 100644 --- a/packages/tool-server/test/artifacts.test.ts +++ b/packages/tool-server/test/artifacts.test.ts @@ -51,8 +51,9 @@ describe("ArtifactStore", () => { const filePath = join(dir, "shot.png"); await writeFile(filePath, Buffer.from([1, 2, 3, 4])); - const handle = await new ArtifactStore().register(filePath); + const handle = await new ArtifactStore().register({ hostPath: filePath, kind: "screenshot" }); expect(handle.__argentArtifact).toBe(true); + expect(handle.kind).toBe("screenshot"); expect(handle.filename).toBe("shot.png"); expect(handle.mimeType).toBe("image/png"); expect(handle.size).toBe(4); @@ -70,10 +71,13 @@ describe("ArtifactStore", () => { try { const filePath = join(dir, "raw.bin"); await writeFile(filePath, "hi"); - const handle = await new ArtifactStore().register(filePath, { + const handle = await new ArtifactStore().register({ + hostPath: filePath, + kind: "screenshot", filename: "pretty.png", mimeType: "image/png", }); + expect(handle.kind).toBe("screenshot"); expect(handle.filename).toBe("pretty.png"); expect(handle.mimeType).toBe("image/png"); } finally { @@ -88,7 +92,11 @@ describe("ArtifactStore", () => { await mkdir(bundle, { recursive: true }); await writeFile(join(bundle, "data.bin"), "trace"); - const handle = await new ArtifactStore().register(bundle); + const handle = await new ArtifactStore().register({ + hostPath: bundle, + kind: "native-profile-trace", + }); + expect(handle.kind).toBe("native-profile-trace"); expect(handle.archive).toBe("tar.gz"); expect(handle.filename).toBe("session.trace"); expect(handle.hostPath).toBe(bundle); @@ -98,9 +106,12 @@ describe("ArtifactStore", () => { }); it("honours an explicit archive option when the path can't be stat'd", async () => { - const handle = await new ArtifactStore().register("/tmp/does-not-exist-yet.trace", { + const handle = await new ArtifactStore().register({ + hostPath: "/tmp/does-not-exist-yet.trace", + kind: "native-profile-trace", archive: "tar.gz", }); + expect(handle.kind).toBe("native-profile-trace"); expect(handle.archive).toBe("tar.gz"); }); @@ -110,11 +121,16 @@ describe("ArtifactStore", () => { const filePath = join(dir, "shot.png"); await writeFile(filePath, "png"); const store = new ArtifactStore(); - const handle = await store.register(filePath, { mimeType: "image/png" }); + const handle = await store.register({ + hostPath: filePath, + kind: "screenshot", + mimeType: "image/png", + }); expect(store.list()).toEqual([ { id: handle.id, + kind: "screenshot", filename: "shot.png", mimeType: "image/png", size: 3, @@ -153,7 +169,11 @@ describe("GET /artifacts", () => { const filePath = join(dir, "img.png"); await writeFile(filePath, "image"); const registry = stubRegistry(); - const artifact = await registry.artifacts.register(filePath, { mimeType: "image/png" }); + const artifact = await registry.artifacts.register({ + hostPath: filePath, + kind: "screenshot", + mimeType: "image/png", + }); isFlagEnabledMock.mockReturnValue(true); handle = createHttpApp(registry); @@ -164,6 +184,7 @@ describe("GET /artifacts", () => { artifacts: [ { id: artifact.id, + kind: "screenshot", filename: "img.png", mimeType: "image/png", size: 5, @@ -193,7 +214,11 @@ describe("GET /artifacts/:id", () => { const bytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0xaa, 0xbb]); await writeFile(filePath, bytes); const registry = stubRegistry(); - const artifact = await registry.artifacts.register(filePath, { mimeType: "image/png" }); + const artifact = await registry.artifacts.register({ + hostPath: filePath, + kind: "screenshot", + mimeType: "image/png", + }); handle = createHttpApp(registry); const res = await supertest(handle.app).get(`/artifacts/${artifact.id}`); @@ -215,7 +240,10 @@ describe("GET /artifacts/:id", () => { await writeFile(join(bundle, "sub", "nested.txt"), "nested"); const registry = stubRegistry(); - const artifact = await registry.artifacts.register(bundle); + const artifact = await registry.artifacts.register({ + hostPath: bundle, + kind: "native-profile-trace", + }); handle = createHttpApp(registry); const res = await supertest(handle.app) .get(`/artifacts/${artifact.id}`) @@ -248,7 +276,7 @@ describe("GET /artifacts/:id", () => { const filePath = join(dir, "gone.png"); await writeFile(filePath, "x"); const registry = stubRegistry(); - const artifact = await registry.artifacts.register(filePath); + const artifact = await registry.artifacts.register({ hostPath: filePath, kind: "screenshot" }); await rm(dir, { recursive: true, force: true }); handle = createHttpApp(registry); diff --git a/packages/tool-server/test/ios-instruments/stop-recovery.test.ts b/packages/tool-server/test/ios-instruments/stop-recovery.test.ts index f035a19d5..fd729f20e 100644 --- a/packages/tool-server/test/ios-instruments/stop-recovery.test.ts +++ b/packages/tool-server/test/ios-instruments/stop-recovery.test.ts @@ -137,15 +137,21 @@ describe("native-profiler-stop recovery branch", () => { expect(mockedExport).toHaveBeenCalledWith(FAKE_TRACE); // exportedFiles are now artifact handles (materialized client-side), not raw paths. - expect(result.exportedFiles.cpu).toMatchObject({ __argentArtifact: true, filename: "cpu.xml" }); + expect(result.exportedFiles.cpu).toMatchObject({ + __argentArtifact: true, + kind: "native-profile-cpu", + filename: "cpu.xml", + }); expect(result.exportedFiles.hangs).toMatchObject({ __argentArtifact: true, + kind: "native-profile-hangs", filename: "hangs.xml", }); // The .trace bundle is now a downloadable artifact (delivered as tar.gz // on demand), not a "stays on the host" note. expect(result.traceFile).toMatchObject({ __argentArtifact: true, + kind: "native-profile-trace", archive: "tar.gz", filename: "ios-profiler-20260101-000000.trace", }); @@ -241,7 +247,10 @@ describe("native-profiler-stop recovery branch", () => { const result = await promise; expect(result.warning).toBeUndefined(); - expect(result.exportedFiles.cpu).toMatchObject({ __argentArtifact: true }); + expect(result.exportedFiles.cpu).toMatchObject({ + __argentArtifact: true, + kind: "native-profile-cpu", + }); expect(api.profilingActive).toBe(false); expect(api.captureProcess).toBeNull(); expect(api.recordingExitedUnexpectedly).toBe(false); diff --git a/packages/tool-server/test/screenshot-diff-tool.test.ts b/packages/tool-server/test/screenshot-diff-tool.test.ts index 3d0a8ea82..8005317dd 100644 --- a/packages/tool-server/test/screenshot-diff-tool.test.ts +++ b/packages/tool-server/test/screenshot-diff-tool.test.ts @@ -88,11 +88,13 @@ describe("screenshotDiffTool", () => { expect(result.summary).toContain("Screenshot diff summary"); expect(result.diffPath).toMatchObject({ __argentArtifact: true, + kind: "screenshot-diff", hostPath: path.join(dir, "current-diff.png"), mimeType: "image/png", }); expect(result.contextDiffPath).toMatchObject({ __argentArtifact: true, + kind: "screenshot-diff-context", hostPath: path.join(dir, "current-context-diff.png"), mimeType: "image/png", }); diff --git a/packages/tool-server/test/screenshot-tool.test.ts b/packages/tool-server/test/screenshot-tool.test.ts index be2b90054..fb0fe9895 100644 --- a/packages/tool-server/test/screenshot-tool.test.ts +++ b/packages/tool-server/test/screenshot-tool.test.ts @@ -41,6 +41,7 @@ describe("screenshot tool", () => { // the unreachable `127.0.0.1` media URL is no longer surfaced. expect(result.image).toMatchObject({ __argentArtifact: true, + kind: "screenshot", filename: "screenshot.png", mimeType: "image/png", hostPath: "/tmp/screenshot.png",