From 68e6301b2c2020c047890ce130cbe6cd2bae164a Mon Sep 17 00:00:00 2001 From: luvs01 Date: Fri, 28 Aug 2026 11:58:46 +0900 Subject: [PATCH] fix(codex): restore collaboration prompt mapping --- src/codex/prompt-text-probe.ts | 30 ++++++++++++++++++--------- tests/codex-prompt-text-probe.test.ts | 22 +++++++++++++++++++- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/codex/prompt-text-probe.ts b/src/codex/prompt-text-probe.ts index 6db8d1e49e..5626a070d6 100644 --- a/src/codex/prompt-text-probe.ts +++ b/src/codex/prompt-text-probe.ts @@ -37,6 +37,9 @@ const LAYER_SECTION_TAGS: Record = { skills: "skills_instructions", apps: "apps_instructions", plugins: "plugins_instructions", + // Context-dependent: it is absent when the active collaboration mode adds no + // instructions, but Codex wraps it in this tag when it does render. + collaboration: "collaboration_mode", environment: "environment_context", permissions: "permissions instructions", // Synthetic: the project doc carries no tag of its own (see extractSections). @@ -59,7 +62,6 @@ const UNMAPPED_LAYER_IDS = [ // truth is that this extractor has no verified tag for them. "personality", "realtime", - "collaboration", // The Rust source names a marker pair, but a world-state section is // DIFF-rendered: it emits nothing on a turn where its state has not changed. Live // `codex debug prompt-input` (codex-cli 0.145.0, 32978 bytes) showed no such block and @@ -176,6 +178,22 @@ function extractSections(raw: string): Map { /** Test seam: the extraction is the part worth pinning, not the spawn. */ export const extractSectionsForTests = extractSections; +function mapSectionsToLayers(sections: Map): Record { + const layers: Record = {}; + for (const [layerId, tag] of Object.entries(LAYER_SECTION_TAGS)) { + const text = sections.get(tag) ?? null; + layers[layerId] = text === null + // Registered but not rendered on this turn, which is an ordinary state for + // a diff-rendered section rather than an error. + ? { text: null, reason: "not-rendered", bytes: 0 } + : { text, reason: "ok", bytes: Buffer.byteLength(text, "utf8") }; + } + return layers; +} + +/** Test seam: pin section-to-layer projection independently of the subprocess. */ +export const mapSectionsToLayersForTests = mapSectionsToLayers; + /** * Probe once and map every known layer to its rendered text. * @@ -202,15 +220,7 @@ export async function probePromptText(timeoutMs = 15_000): Promise = {}; - for (const [layerId, tag] of Object.entries(LAYER_SECTION_TAGS)) { - const text = sections.get(tag) ?? null; - layers[layerId] = text === null - // Registered but not rendered on this turn, which is an ordinary state for - // a diff-rendered section rather than an error. - ? { text: null, reason: "not-rendered", bytes: 0 } - : { text, reason: "ok", bytes: Buffer.byteLength(text, "utf8") }; - } + const layers = mapSectionsToLayers(sections); // A file that exists and is empty is not the same as a layer that chose to send // nothing. Reporting "sent nothing" for an empty AGENTS.md tells the user their diff --git a/tests/codex-prompt-text-probe.test.ts b/tests/codex-prompt-text-probe.test.ts index 11f453a6c8..a52f45374e 100644 --- a/tests/codex-prompt-text-probe.test.ts +++ b/tests/codex-prompt-text-probe.test.ts @@ -7,7 +7,10 @@ * that attribution to a user as an explanation. */ import { describe, expect, test } from "bun:test"; -import { extractSectionsForTests } from "../src/codex/prompt-text-probe"; +import { + extractSectionsForTests, + mapSectionsToLayersForTests, +} from "../src/codex/prompt-text-probe"; function message(text: string): string { return JSON.stringify([{ type: "message", role: "developer", content: [{ type: "input_text", text }] }]); @@ -41,6 +44,23 @@ describe("section extraction", () => { expect(sections.get("apps_instructions")).toBe("line one\nline two"); }); + test("context-dependent collaboration text maps to its prompt layer", () => { + const rendered = extractSectionsForTests( + message("Pair-programming instructions."), + ); + expect(mapSectionsToLayersForTests(rendered).collaboration).toEqual({ + text: "Pair-programming instructions.", + reason: "ok", + bytes: 30, + }); + + expect(mapSectionsToLayersForTests(new Map()).collaboration).toEqual({ + text: null, + reason: "not-rendered", + bytes: 0, + }); + }); + test("AGENTS.md is bounded by its own INSTRUCTIONS wrapper", () => { // Capturing to end-of-message swept up whatever untagged prose followed. The // body is delimited, so the delimiter is the boundary.