diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e8de64004..a945bb1ac 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,12 +2,12 @@ ## [Unreleased] -## [16.0.5] - 2026-06-17 - ### Added - Added `friendlyName` support for hidden secrets so model-visible placeholders can carry sanitized semantic labels, content-derived hashes, and case hints while preserving exact deobfuscation ([#2465](https://github.com/can1357/oh-my-pi/issues/2465)). +## [16.0.5] - 2026-06-17 + ### Added - Added `tui.tight` setting (default `false`) to enable tight layout by removing the 1-character horizontal padding from terminal output. diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index a0e43c27f..057a96c00 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -1222,17 +1222,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir); const envEntries = collectEnvSecrets(); const allEntries = [...envEntries, ...fileEntries]; + const needsPlaceholderKey = allEntries.some(entry => (entry.mode ?? "obfuscate") === "obfuscate"); + const placeholderKey = needsPlaceholderKey + ? await getSecretPlaceholderKey() + : await getExistingSecretPlaceholderKey(); if (allEntries.length > 0) { // The persisted placeholder key — and creating its key file under the // config root — is only needed for reversible obfuscate-mode placeholders. // Replace-mode entries never build a keyed base, so a replace-only secrets // set must not require the key; otherwise a headless run with an unwritable // config root fails startup for a feature it does not use. - const needsPlaceholderKey = allEntries.some(entry => (entry.mode ?? "obfuscate") === "obfuscate"); - const placeholderKey = needsPlaceholderKey - ? await getSecretPlaceholderKey() - : await getExistingSecretPlaceholderKey(); obfuscator = new SecretObfuscator(allEntries, placeholderKey); + } else if (placeholderKey !== undefined) { + obfuscator = new SecretObfuscator( + [{ type: "plain", mode: "replace", content: placeholderKey }], + placeholderKey, + ); } } const secretsEnabled = obfuscator?.hasSecrets() === true; diff --git a/packages/coding-agent/test/sdk-session-isolation.test.ts b/packages/coding-agent/test/sdk-session-isolation.test.ts index 1eab2c143..9505b204e 100644 --- a/packages/coding-agent/test/sdk-session-isolation.test.ts +++ b/packages/coding-agent/test/sdk-session-isolation.test.ts @@ -170,11 +170,18 @@ describe("createAgentSession session storage isolation", () => { enableLsp: false, }; - const withoutSecrets = await createAgentSession(commonOptions); + const existingKeySpy = spyOn(secrets, "getExistingSecretPlaceholderKey").mockImplementation( + async () => undefined, + ); try { - expect(withoutSecrets.session.systemPrompt.join("\n")).not.toContain(redactionGuidance); + const withoutSecrets = await createAgentSession(commonOptions); + try { + expect(withoutSecrets.session.systemPrompt.join("\n")).not.toContain(redactionGuidance); + } finally { + await withoutSecrets.session.dispose(); + } } finally { - await withoutSecrets.session.dispose(); + existingKeySpy.mockRestore(); } fs.mkdirSync(path.join(cwd, ".omp"), { recursive: true }); @@ -258,7 +265,7 @@ describe("createAgentSession session storage isolation", () => { }); }); - it("requests the placeholder key only when an obfuscate-mode secret is configured", async () => { + it("creates the placeholder key only when an obfuscate-mode secret is configured", async () => { await withClearedSecretEnv(async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-secrets-key-${Snowflake.next()}-`)); tempDirs.push(tempDir); @@ -287,6 +294,18 @@ describe("createAgentSession session storage isolation", () => { async () => "existing-placeholder-key", ); try { + const keyOnly = await createAgentSession(commonOptions); + try { + expect(keySpy).not.toHaveBeenCalled(); + expect(existingKeySpy).toHaveBeenCalled(); + expect(keyOnly.session.obfuscator?.obfuscate("existing-placeholder-key")).not.toContain( + "existing-placeholder-key", + ); + } finally { + await keyOnly.session.dispose(); + } + + existingKeySpy.mockClear(); // Replace-mode secrets never build a reversible keyed placeholder, so // startup must not create the key file; an existing key is still redacted. fs.writeFileSync(