diff --git a/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.test.ts b/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.test.ts new file mode 100644 index 00000000000..40d13e7b403 --- /dev/null +++ b/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.test.ts @@ -0,0 +1,165 @@ +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { createRuntimeStore, type AcpSessionRecord, type AcpSessionStore } from "acpx/runtime"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createEphemeralSessionEnvironmentStore } from "./ephemeral-session-environment.js"; + +const currentEnvironment = Object.freeze({ + PAPERCLIP_API_KEY: "current-credential-fixture", + PAPERCLIP_RUN_ID: "current-run-fixture", + PATH: "/fixture/current-bin", +}); +const oldEnvironment = { PAPERCLIP_API_KEY: "old-credential-fixture", OLD_RUN_PATH: "/fixture/old-run" }; + +function record(): AcpSessionRecord { + return { + schema: "acpx.session.v1", + acpxRecordId: "fixture-record", + acpSessionId: "fixture-session", + agentSessionId: "fixture-agent-session", + agentCommand: "fixture-provider", + cwd: "/fixture/project", + name: "fixture-name", + createdAt: "2026-10-03T00:00:00.000Z", + lastUsedAt: "2026-10-03T00:00:01.000Z", + lastSeq: 3, + eventLog: { active_path: "/fixture/events.jsonl", segment_count: 1, max_segment_bytes: 1024, max_segments: 2 }, + messages: [{ User: { id: "message-1", content: [{ Text: "Retained conversation" }] } }], + updated_at: "2026-10-03T00:00:01.000Z", + cumulative_token_usage: { input_tokens: 11, output_tokens: 7 }, + request_token_usage: { request_1: { input_tokens: 11 } }, + importedFrom: { recordId: "origin-record", cwdOriginal: "/fixture/origin", exportedBy: "fixture-caller", exportedAt: "2026-10-03T00:00:00.000Z" }, + acpx: { + current_mode_id: "code", + desired_config_options: { effort: "high" }, + session_options: { model: "fixture-model", allowed_tools: ["Read", "Write"], max_turns: 4, system_prompt: { append: "Retained caller option" }, env: { ...currentEnvironment } }, + }, + }; +} +function freeze(value: T): T { + for (const child of Object.values(value)) if (typeof child === "object" && child !== null) freeze(child); + return Object.freeze(value); +} +function stubStore(loaded: AcpSessionRecord | undefined) { + const load = vi.fn().mockResolvedValue(loaded); + const save = vi.fn().mockResolvedValue(undefined); + return { load, save }; +} +const directories: string[] = []; +afterEach(async () => { await Promise.all(directories.splice(0).map(directory => rm(directory, { recursive: true, force: true }))); }); +async function diskStore() { + const stateDir = await mkdtemp(join(tmpdir(), "paperclip-session-env-")); directories.push(stateDir); + return { store: createRuntimeStore({ stateDir }), file: join(stateDir, "sessions", "fixture-record.json") }; +} + +describe("ephemeral ACPX session environment", () => { + it("saves a fresh session to the actual file store without persisting its live environment", async () => { + const disk = await diskStore(), input = freeze(record()); + const wrapper = createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment); + await wrapper.save(input); + const text = await readFile(disk.file, "utf8"), saved = JSON.parse(text); + expect(saved.acpx.session_options).not.toHaveProperty("env"); + for (const value of Object.values(currentEnvironment)) expect(text).not.toContain(value); + expect(input.acpx?.session_options?.env).toEqual(currentEnvironment); + expect(saved.acpx.current_mode_id).toBe("code"); + expect(saved.acpx.desired_config_options).toEqual(input.acpx?.desired_config_options); + expect(saved.acpx.session_options).toEqual({ model: "fixture-model", allowed_tools: ["Read", "Write"], max_turns: 4, system_prompt: { append: "Retained caller option" } }); + expect(saved.messages).toEqual(input.messages); + expect(saved.imported_from.record_id).toBe("origin-record"); + const loaded = await wrapper.load(input.acpxRecordId); + expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment); + expect(loaded?.messages).toEqual(input.messages); + }); + + it("loads an old env-bearing file with rotated credentials without writing, then saves neither environment", async () => { + const disk = await diskStore(), legacy = record(); legacy.acpx!.session_options!.env = { ...oldEnvironment }; + await disk.store.save(legacy); + const before = await readFile(disk.file, "utf8"); + const wrapper = createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment); + const loaded = await wrapper.load(legacy.acpxRecordId); + expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment); + expect(loaded?.acpx?.session_options?.env).not.toHaveProperty("OLD_RUN_PATH"); + expect(await readFile(disk.file, "utf8")).toBe(before); + expect(legacy.acpx?.session_options?.env).toEqual(oldEnvironment); + await wrapper.save(loaded!); + const after = await readFile(disk.file, "utf8"); + for (const value of [...Object.values(oldEnvironment), ...Object.values(currentEnvironment)]) expect(after).not.toContain(value); + expect(JSON.parse(after).acpx.session_options).not.toHaveProperty("env"); + expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment); + }); + + it("loads an env-free persisted session into the current environment without writing", async () => { + const disk = await diskStore(), input = record(); delete input.acpx!.session_options!.env; + await disk.store.save(input); + const before = await readFile(disk.file, "utf8"); + const loaded = await createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment).load(input.acpxRecordId); + expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment); + expect(loaded?.acpx?.session_options?.model).toBe("fixture-model"); + expect(await readFile(disk.file, "utf8")).toBe(before); + }); + + it("copies loaded records, options and current env without mutating stored or caller-owned objects", async () => { + const stored = record(); stored.acpx!.session_options!.env = { ...oldEnvironment }; freeze(stored); + const backing = stubStore(stored), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment); + const loaded = await wrapper.load("fixture-record"); + expect(backing.load).toHaveBeenCalledWith("fixture-record"); expect(backing.save).not.toHaveBeenCalled(); + expect(loaded).not.toBe(stored); expect(loaded?.acpx).not.toBe(stored.acpx); + expect(loaded?.acpx?.session_options).not.toBe(stored.acpx?.session_options); + expect(loaded?.acpx?.session_options?.env).not.toBe(currentEnvironment); + expect(stored.acpx?.session_options?.env).toEqual(oldEnvironment); + loaded!.acpx!.session_options!.env!.PAPERCLIP_API_KEY = "isolated-loaded-fixture"; + expect(currentEnvironment.PAPERCLIP_API_KEY).toBe("current-credential-fixture"); + }); + + it("saves only a copied env-free projection, preserving unrelated and caller metadata", async () => { + const input = freeze(Object.assign(record(), { caller_metadata: { keep: "caller-state", env: { preserve: "unrelated caller metadata" } } })); + const backing = stubStore(undefined), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment); + await wrapper.save(input); + const saved = backing.save.mock.calls[0]![0]; + const { env: _environment, ...expectedOptions } = input.acpx!.session_options!; + expect(saved).toEqual({ ...input, acpx: { ...input.acpx, session_options: expectedOptions } }); + expect(saved).not.toBe(input); expect(saved.acpx).not.toBe(input.acpx); + expect(saved.acpx?.session_options).not.toBe(input.acpx?.session_options); + expect(saved.acpx?.session_options).not.toHaveProperty("env"); + expect(input.acpx?.session_options?.env).toEqual(currentEnvironment); + }); + + for (const [name, acpx] of [ + ["absent acpx", undefined], + ["explicit undefined acpx", undefined], + ["absent session options", { current_mode_id: "code" }], + ["undefined session options", { session_options: undefined }], + ["empty session options", { session_options: {} }], + ["undefined env", { session_options: { env: undefined, model: "fixture-model" } }], + ] as const) it(`preserves ${name} when saving and retains existing load injection semantics`, async () => { + const input = record(); + if (name === "absent acpx") delete input.acpx; else input.acpx = acpx; + freeze(input); + const backing = stubStore(input), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment); + await wrapper.save(input); + const saved = backing.save.mock.calls[0]![0]; + if (name === "undefined env") expect(saved.acpx?.session_options).toEqual({ model: "fixture-model" }); + else expect(saved).toEqual(input); + expect(Object.hasOwn(saved, "acpx")).toBe(Object.hasOwn(input, "acpx")); + if (saved.acpx && input.acpx) expect(Object.hasOwn(saved.acpx, "session_options")).toBe(Object.hasOwn(input.acpx, "session_options")); + const loaded = await wrapper.load(input.acpxRecordId); + expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment); + expect(backing.save).toHaveBeenCalledTimes(1); + }); + + it("returns a missing record without saving or creating conversation state", async () => { + const backing = stubStore(undefined); + expect(await createEphemeralSessionEnvironmentStore(backing, currentEnvironment).load("missing")).toBeUndefined(); + expect(backing.save).not.toHaveBeenCalled(); + }); + + it("propagates load and save errors unchanged", async () => { + const failure = new Error("fixture store failure"), backing = stubStore(undefined); + backing.load.mockRejectedValue(failure); backing.save.mockRejectedValue(failure); + const wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment); + await expect(wrapper.load("fixture-record")).rejects.toBe(failure); + const input = freeze(record()); await expect(wrapper.save(input)).rejects.toBe(failure); + expect(input.acpx?.session_options?.env).toEqual(currentEnvironment); + }); +}); diff --git a/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.ts b/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.ts new file mode 100644 index 00000000000..3bd59942ec9 --- /dev/null +++ b/packages/adapter-utils/src/acpx-engine/ephemeral-session-environment.ts @@ -0,0 +1,32 @@ +import type { AcpSessionStore } from "acpx/runtime"; + +/** Keep the current run's environment in memory, outside persisted conversation state. */ +export function createEphemeralSessionEnvironmentStore( + persistedStore: AcpSessionStore, + currentEnvironment: Readonly>, +): AcpSessionStore { + return { + async load(id) { + const record = await persistedStore.load(id); + if (!record) return undefined; + return { + ...record, + acpx: { + ...record.acpx, + session_options: { + ...record.acpx?.session_options, + env: { ...currentEnvironment }, + }, + }, + }; + }, + save(record) { + const persisted = { ...record }; + if (record.acpx?.session_options !== undefined) { + const { env: _environment, ...sessionOptions } = record.acpx.session_options; + persisted.acpx = { ...record.acpx, session_options: sessionOptions }; + } + return persistedStore.save(persisted); + }, + }; +} diff --git a/packages/adapter-utils/src/acpx-engine/execute.test.ts b/packages/adapter-utils/src/acpx-engine/execute.test.ts index 320c2584eaf..03a42f96455 100644 --- a/packages/adapter-utils/src/acpx-engine/execute.test.ts +++ b/packages/adapter-utils/src/acpx-engine/execute.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { AcpRuntimeOptions } from "acpx/runtime"; +import { createRuntimeStore, type AcpRuntimeOptions, type AcpSessionRecord, type AcpSessionStore } from "acpx/runtime"; import type { AdapterExecutionContext, AdapterRuntimeMcpAccess } from "@paperclipai/adapter-utils"; import { DEFAULT_REMOTE_SANDBOX_ADAPTER_TIMEOUT_SEC, @@ -377,6 +377,42 @@ const ALLOWED_TURN_SPAN_ATTRIBUTE_KEYS = new Set([ ]); describe("shared ACPX engine runtime behavior", () => { + it("integrates the env-free store while delivering current credentials and options to the provider", async () => { + const root = await makeTempRoot(); + const stateDir = path.join(root, "state"); + const first = await runExecutor({ agent: "claude", agentCommand: "node ./fake-acp.js", cwd: root, stateDir, + env: { ANTHROPIC_API_KEY: "first-fixture-credential" } }); + const liveEnvironment = (first.sessionInputs[0]!.sessionOptions as { env: Record }).env; + expect(liveEnvironment.ANTHROPIC_API_KEY).toBe("first-fixture-credential"); + const store = first.runtimeOptions[0]!.sessionStore as AcpSessionStore; + const record: AcpSessionRecord = { + schema: "acpx.session.v1", acpxRecordId: "integration-record", acpSessionId: "integration-session", + agentCommand: "fixture-provider", cwd: root, name: "fixture", createdAt: "2026-10-03T00:00:00.000Z", + lastUsedAt: "2026-10-03T00:00:01.000Z", lastSeq: 0, + eventLog: { active_path: path.join(stateDir, "fixture.jsonl"), segment_count: 1, max_segment_bytes: 1024, max_segments: 2 }, + messages: [], updated_at: "2026-10-03T00:00:01.000Z", cumulative_token_usage: {}, request_token_usage: {}, + acpx: { session_options: { env: { ...liveEnvironment }, model: "retained-model", max_turns: 4 } }, + }; + await store.save(record); + const file = path.join(stateDir, "sessions", "integration-record.json"); + const saved = await fs.readFile(file, "utf8"); + expect(saved).not.toContain("first-fixture-credential"); + expect(JSON.parse(saved).acpx.session_options).toEqual({ model: "retained-model", max_turns: 4 }); + expect(record.acpx?.session_options?.env).toEqual(liveEnvironment); + // A file written by the prior engine is loaded with the current run's env, + // without altering the provider launch or deleting unrelated options. + record.acpx!.session_options!.env = { ANTHROPIC_API_KEY: "legacy-fixture-credential" }; + await createRuntimeStore({ stateDir }).save(record); + const second = await runExecutor({ agent: "claude", agentCommand: "node ./fake-acp.js", cwd: root, stateDir, + env: { ANTHROPIC_API_KEY: "rotated-fixture-credential" } }, { runtime: { sessionParams: first.result.sessionParams } }); + expect((second.sessionInputs[0]!.sessionOptions as { env: Record }).env.ANTHROPIC_API_KEY).toBe("rotated-fixture-credential"); + const rotatedStore = second.runtimeOptions[0]!.sessionStore as AcpSessionStore; + const loaded = await rotatedStore.load(record.acpxRecordId); + expect(loaded?.acpx?.session_options).toMatchObject({ env: { ANTHROPIC_API_KEY: "rotated-fixture-credential" }, model: "retained-model", max_turns: 4 }); + await rotatedStore.save(loaded!); + const resaved = await fs.readFile(file, "utf8"); + for (const value of ["first-fixture-credential", "legacy-fixture-credential", "rotated-fixture-credential"]) expect(resaved).not.toContain(value); + }); it.each(["claude", "codex", "gemini", "kimi", "custom"])("defaults the legacy %s engine to full auto on fresh and resumed runs", async (agent) => { const root = await makeTempRoot(); const config = { @@ -3784,9 +3820,54 @@ describe("ACPX engine Claude skill bundle staging (remote ACP lane)", () => { }; } + it("advertises folded routing metadata and exact staged paths without injecting skill bodies", async () => { + const root = await makeTempRoot(); + const cwd = path.join(root, "worktree"); + const stateDir = path.join(root, "state"); + await fs.mkdir(cwd, { recursive: true }); + const skill = await createSkill(path.join(root, "sources"), "paperclip--versioned", [ + "---", "description: >-", " Use for Paperclip-managed tasks", " and durable task documents.", + "---", "PRIVATE_SKILL_BODY_RECIPE", "", + ].join("\n")); + const { meta, result } = await runExecutor({ + agent: "claude", agentCommand: "node ./fake-acp.js", stateDir, cwd, + paperclipRuntimeSkills: [skill], paperclipSkillSync: { desiredSkills: [skill.key] }, + }); + const bundle = await onlyChildDir(path.join(stateDir, "runtime-skills", "claude")); + const stagedFile = path.join(bundle, ".claude", "skills", skill.runtimeName, "SKILL.md"); + const prompt = String(meta[0]?.prompt ?? ""); + expect(prompt).toContain(`- ${skill.runtimeName}: Use for Paperclip-managed tasks and durable task documents. (file: ${stagedFile})`); + expect(prompt).not.toContain("PRIVATE_SKILL_BODY_RECIPE"); + expect(prompt).not.toContain(skill.source); + expect(result.sessionParams?.skills).toMatchObject({ selectedSkills: [skill.runtimeName] }); + await expect(fs.readFile(stagedFile, "utf8")).resolves.toContain("PRIVATE_SKILL_BODY_RECIPE"); + }); + + it("bounds routing descriptions and keeps files discoverable when metadata is absent or malformed", async () => { + const root = await makeTempRoot(); + const cwd = path.join(root, "worktree"); + const stateDir = path.join(root, "state"); + await fs.mkdir(cwd, { recursive: true }); + const sources = path.join(root, "sources"); + const bounded = await createSkill(sources, "bounded", `---\ndescription: ${"x".repeat(600)}\n---\nBOUNDED_BODY\n`); + const absent = await createSkill(sources, "absent", "# ABSENT_BODY\n"); + const malformed = await createSkill(sources, "malformed", "---\ndescription: incomplete header\nMALFORMED_BODY\n"); + const skills = [bounded, absent, malformed]; + const { meta } = await runExecutor({ + agent: "claude", agentCommand: "node ./fake-acp.js", stateDir, cwd, + paperclipRuntimeSkills: skills, paperclipSkillSync: { desiredSkills: skills.map((skill) => skill.key) }, + }); + const prompt = String(meta[0]?.prompt ?? ""); + expect(prompt).toContain(`- bounded: ${"x".repeat(512)} (file: `); + expect(prompt).not.toContain("x".repeat(513)); + expect(prompt).toMatch(/- absent \(file: .*\/absent\/SKILL\.md\)/); + expect(prompt).toMatch(/- malformed \(file: .*\/malformed\/SKILL\.md\)/); + for (const body of ["BOUNDED_BODY", "ABSENT_BODY", "MALFORMED_BODY"]) expect(prompt).not.toContain(body); + }); + it("rewrites the prompt and skill identity onto the in-sandbox skill root once the bundle is staged", async () => { const { stateDir, localCwd, executionTarget } = await setupRemoteSandbox(); - const skill = await createSkill(path.join(localCwd, "skills"), "review"); + const skill = await createSkill(path.join(localCwd, "skills"), "review", "---\ndescription: Review requested task documents.\n---\n# review\nREMOTE_BODY_ONLY\n"); const { meta, result } = await runExecutor( { @@ -3809,6 +3890,8 @@ describe("ACPX engine Claude skill bundle staging (remote ACP lane)", () => { const inSandboxSkillsRoot = prompt.match(/Skill root: (\S+)/)![1]!; expect(inSandboxSkillsRoot).not.toBe(hostSkillsHome); + expect(prompt).toContain(`- review: Review requested task documents. (file: ${path.join(inSandboxSkillsRoot, "review", "SKILL.md")})`); + expect(prompt).not.toContain("REMOTE_BODY_ONLY"); await expect( fs.readFile(path.join(inSandboxSkillsRoot, skill.runtimeName, "SKILL.md"), "utf8"), ).resolves.toContain("# review"); diff --git a/packages/adapter-utils/src/acpx-engine/execute.ts b/packages/adapter-utils/src/acpx-engine/execute.ts index 5c575ca8c5b..9f25c6ba5fa 100644 --- a/packages/adapter-utils/src/acpx-engine/execute.ts +++ b/packages/adapter-utils/src/acpx-engine/execute.ts @@ -7,6 +7,7 @@ import path from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { createHash, randomUUID } from "node:crypto"; +import { parseFrontmatterMarkdown } from "@paperclipai/shared"; import { fileURLToPath } from "node:url"; import type { AdapterBillingType, @@ -85,6 +86,7 @@ import { type PaperclipSkillEntry, } from "@paperclipai/adapter-utils/server-utils"; import { shellQuote } from "@paperclipai/adapter-utils/ssh"; +import { createEphemeralSessionEnvironmentStore } from "./ephemeral-session-environment.js"; import { createAcpRuntime, createAgentRegistry, @@ -100,7 +102,6 @@ import { type AcpRuntimeTurnResult, type AcpRuntimeUsageBreakdown, type AcpRuntimeUsageCost, - type AcpSessionStore, } from "acpx/runtime"; import { ACPX_DUPLEX_LOSS_CANCEL_DEADLINE_MS, @@ -1183,11 +1184,24 @@ async function prepareClaudeSkillRuntime(input: { } const selectedNames = materializedNames.sort(); + const skillDescriptions = await Promise.all(selectedNames.map(async (name) => { + const file = path.join(skillsHome, name, "SKILL.md"); + let description = ""; + try { + const parsed = parseFrontmatterMarkdown(await fs.readFile(file, "utf8")); + description = asString(parsed.frontmatter.description, "").replace(/\s+/g, " ").trim().slice(0, 512); + } catch { + // A readable skill without valid routing metadata remains available. + // Do not substitute its body for a missing description. + } + return `- ${name}${description ? `: ${description}` : ""} (file: ${file})`; + })); const promptInstructions = selectedNames.length > 0 ? [ "Paperclip has materialized selected runtime skills for this ACPX Claude session.", `Skill root: ${skillsHome}`, `Selected skills: ${selectedNames.join(", ")}`, + ...skillDescriptions, "When a task calls for one of these skills, read its SKILL.md from that root and follow it.", ].join("\n") : ""; @@ -4249,26 +4263,9 @@ export function createAcpxEngineExecutor(deps: AcpxEngineExecutorOptions = {}) { flushChildStderr(childStderrState); childStderrState.logPath = prepared.childStderrLogPath; const persistedRuntimeStore = createRuntimeStore({ stateDir: prepared.stateDir }); - const runtimeStore: AcpSessionStore = { - async load(id) { - const record = await persistedRuntimeStore.load(id); - if (!record) return undefined; - // ACPX resumes from the stored session options rather than the - // options passed to ensureSession. Keep conversation state, but - // launch the provider with this run's credentials and scratch paths. - return { - ...record, - acpx: { - ...record.acpx, - session_options: { - ...record.acpx?.session_options, - env: { ...prepared.env }, - }, - }, - }; - }, - save: (record) => persistedRuntimeStore.save(record), - }; + // Resume with this run's launch environment; keep it out of the saved + // conversation record without mutating the live runtime's options. + const runtimeStore = createEphemeralSessionEnvironmentStore(persistedRuntimeStore, prepared.env); const runtimeOptions: PaperclipAcpRuntimeOptions = { cwd: prepared.cwd, // Host-only spawn cwd for the relay proxy on the remote process-session diff --git a/tests/runner-e2e/native-completion-defaults.test.ts b/tests/runner-e2e/native-completion-defaults.test.ts index 21c5c6dc082..8777a2cb39b 100644 --- a/tests/runner-e2e/native-completion-defaults.test.ts +++ b/tests/runner-e2e/native-completion-defaults.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; +import { createHash } from "node:crypto"; import { captureNativeDefault, gradeNativeDefault, nativeCompletionProfile, nativeCompletionWorkspaceDigest, NATIVE_MASTER_DEFAULT_SHA256, type NativeDefaultReceipt } from "./native-completion-defaults.js"; import { mkdtemp, mkdir, writeFile, symlink, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -58,7 +59,7 @@ describe("native production defaults", () => { expect(original.instructionsBundle).toBeDefined(); expect(() => nativeCompletionProfile({ ...profile, generation: "legacy" })).toThrow(); }); - it("hashes actual public bundle bytes before execution without publishing the body", async () => { + it("captures current default configuration independently of the frozen native qualification contract", async () => { const content = readFileSync(new URL("../../server/src/onboarding-assets/default/AGENTS.md", import.meta.url), "utf8"); const called: string[] = []; const api = { async get(path: string): Promise { @@ -67,7 +68,12 @@ describe("native production defaults", () => { : path.includes("instructions-bundle/file") ? { content } : { budgetMonthlyCents: 1000 }) as T; } }; const actual = await captureNativeDefault({ api, agentId: "agent", companyId: "company" }); - expect(gradeNativeDefault(actual).passed).toBe(true); + const currentSha256 = createHash("sha256").update(content).digest("hex"); + expect(actual.files).toEqual([{ path: "AGENTS.md", sha256: currentSha256, bytes: Buffer.byteLength(content) }]); + // The archived native comparison intentionally pins its original master + // context. Accurate capture of a later configuration does not qualify it + // against those historical live results. + expect(gradeNativeDefault(actual).passed).toBe(currentSha256 === NATIVE_MASTER_DEFAULT_SHA256); expect(called).toContain("/api/agents/agent/instructions-bundle/file?path=AGENTS.md"); expect(JSON.stringify(actual)).not.toContain(content); });