diff --git a/gui/src/components/codex-set/PromptLayerDialog.tsx b/gui/src/components/codex-set/PromptLayerDialog.tsx index 3b8ae51920..3d6382931b 100644 --- a/gui/src/components/codex-set/PromptLayerDialog.tsx +++ b/gui/src/components/codex-set/PromptLayerDialog.tsx @@ -154,6 +154,8 @@ export default function PromptLayerDialog({ ? t("codexSet.dialog.emptySource", { path: text.sourcePath ?? "" }) : text?.reason === "not-rendered" ? t("codexSet.dialog.notRendered") + : text?.reason === "unmapped" + ? t("codexSet.dialog.unmapped") : text?.reason === "not-exposed" ? t("codexSet.dialog.notExposed") : t("codexSet.dialog.textUnavailable")} diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 5bea652194..242363766f 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -943,6 +943,7 @@ export const de: Record = { "codexSet.dialog.notRendered": "In der gelesenen Runde hat diese Ebene nichts gesendet. Abschnitte werden nur bei Änderungen erneut gesendet, daher kann eine einzelne Stichprobe sie nicht enthalten.", "codexSet.dialog.emptySource": "Die Datei unter {path} existiert, ist aber leer, daher sendet diese Ebene nichts.", "codexSet.dialog.notExposed": "Der Basis-Prompt läuft außerhalb der Nachrichtenliste, die Codex ausgeben kann, und lässt sich hier nicht anzeigen. Ersetzen ist über model_instructions_file möglich.", + "codexSet.dialog.unmapped": "Für diese Ebene gibt es noch keine bestätigte Zuordnung in der Prompt-Ausgabe, daher kann ihr Text hier noch nicht angezeigt werden.", "codexSet.dialog.textUnavailable": "Der Codex-Prompt konnte auf diesem Rechner nicht gelesen werden, daher ist der Text nicht verfügbar.", "codexSet.class.base": "Basisanweisungen", "codexSet.class.config-toggle": "Hier umschaltbar", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 0e27ca29e2..b0a4f8b244 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1437,6 +1437,7 @@ export const en = { "codexSet.dialog.notRendered": "This layer sent nothing on the turn we read. Sections are only re-sent when they change, so an unchanged layer is absent from a single sample.", "codexSet.dialog.emptySource": "The file at {path} exists but is empty, so this layer sends nothing.", "codexSet.dialog.notExposed": "The base prompt travels outside the message list Codex can print, so it cannot be shown here. Replacing it is possible through model_instructions_file.", + "codexSet.dialog.unmapped": "This layer has no confirmed mapping in the prompt output, so its text cannot be shown here yet.", "codexSet.dialog.textUnavailable": "The Codex prompt could not be read on this machine, so the text is unavailable.", "codexSet.class.base": "Base instructions", "codexSet.class.config-toggle": "Switchable here", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 4c5292014b..cbb278706c 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1410,6 +1410,7 @@ export const fr: Record = { "codexSet.dialog.notRendered": "Lors du tour que nous avons lu, cette couche n'a rien envoyé. Les sections ne sont renvoyées qu'en cas de changement : un seul échantillon peut donc ne pas la contenir.", "codexSet.dialog.emptySource": "Le fichier {path} existe mais il est vide : cette couche n'envoie donc rien.", "codexSet.dialog.notExposed": "L'invite de base circule en dehors de la liste de messages que Codex peut afficher ; elle ne peut donc pas être montrée ici. On peut la remplacer via model_instructions_file.", + "codexSet.dialog.unmapped": "Cette couche n'a pas encore de correspondance confirmée dans la sortie de l'invite ; son texte ne peut donc pas encore être affiché ici.", "codexSet.dialog.textUnavailable": "L'invite Codex n'a pas pu être lue sur cette machine, le texte est donc indisponible.", "codexSet.class.base": "Instructions de base", "codexSet.class.config-toggle": "Modifiable ici", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index 5be35ce9e4..13ca3abd13 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1370,6 +1370,7 @@ export const ja: Record = { "codexSet.dialog.notRendered": "確認したターンでは、このレイヤーは何も送信していません。各セクションは内容が変わったときだけ再送されるため、一度の確認では欠けて見えることがあります。", "codexSet.dialog.emptySource": "{path} のファイルは存在しますが空のため、このレイヤーは何も送信しません。", "codexSet.dialog.notExposed": "基本プロンプトは Codex が出力するメッセージ一覧の外を通るため、ここには表示できません。model_instructions_file で置き換えることは可能です。", + "codexSet.dialog.unmapped": "このレイヤーにはプロンプト出力内で確認済みの対応付けがまだないため、ここにはテキストを表示できません。", "codexSet.dialog.textUnavailable": "このマシンでは Codex のプロンプトを読み取れず、原文を表示できません。", "codexSet.class.base": "基本指示", "codexSet.class.config-toggle": "ここで切り替え可能", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index d7def6f1c7..228b6e3c5a 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -969,6 +969,7 @@ export const ko: Record = { "codexSet.dialog.notRendered": "확인한 턴에서는 이 레이어가 아무것도 보내지 않았습니다. 각 섹션은 내용이 바뀔 때만 다시 전송되므로, 한 번 확인한 것만으로는 빠져 보일 수 있습니다.", "codexSet.dialog.emptySource": "{path} 파일은 있지만 비어 있어서 이 레이어는 아무것도 보내지 않습니다.", "codexSet.dialog.notExposed": "기본 프롬프트는 Codex가 출력하는 메시지 목록 밖으로 전달되어 여기서 보여줄 수 없습니다. 대신 model_instructions_file로 교체할 수 있습니다.", + "codexSet.dialog.unmapped": "이 레이어는 프롬프트 출력에서 확인된 매핑이 아직 없어 여기에서 텍스트를 표시할 수 없습니다.", "codexSet.dialog.textUnavailable": "이 컴퓨터에서 Codex 프롬프트를 읽지 못해 원문을 표시할 수 없습니다.", "codexSet.class.base": "기본 지침", "codexSet.class.config-toggle": "여기서 전환 가능", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 97f77fcdca..a444b72222 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1421,6 +1421,7 @@ export const ru: Record = { "codexSet.dialog.notRendered": "На прочитанном нами шаге этот слой ничего не отправил. Разделы пересылаются только при изменении, поэтому в одной выборке слой может отсутствовать.", "codexSet.dialog.emptySource": "Файл {path} существует, но пуст, поэтому этот слой ничего не отправляет.", "codexSet.dialog.notExposed": "Базовый промпт передаётся вне списка сообщений, который Codex может напечатать, поэтому показать его здесь нельзя. Заменить его можно через model_instructions_file.", + "codexSet.dialog.unmapped": "Для этого слоя пока нет подтверждённого сопоставления в выводе промпта, поэтому его текст ещё нельзя показать здесь.", "codexSet.dialog.textUnavailable": "На этой машине не удалось прочитать промпт Codex, поэтому текст недоступен.", "codexSet.class.base": "Базовые инструкции", "codexSet.class.config-toggle": "Переключается здесь", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 57e40312d0..ebc3ff7014 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1428,6 +1428,7 @@ export const tr: Record = { "codexSet.dialog.notRendered": "Okuduğumuz turda bu katman hiçbir şey göndermedi. Bölümler yalnızca değiştiklerinde yeniden gönderilir, bu yüzden tek bir örnekte görünmeyebilir.", "codexSet.dialog.emptySource": "{path} konumundaki dosya var ancak boş, bu yüzden bu katman hiçbir şey göndermiyor.", "codexSet.dialog.notExposed": "Temel istem, Codex'in yazdırabildiği mesaj listesinin dışından geçtiği için burada gösterilemez. model_instructions_file ile değiştirilebilir.", + "codexSet.dialog.unmapped": "Bu katmanın istem çıktısında henüz doğrulanmış bir eşlemesi yok, bu nedenle metni burada henüz gösterilemiyor.", "codexSet.dialog.textUnavailable": "Bu makinede Codex istemi okunamadı, bu yüzden metin kullanılamıyor.", "codexSet.class.base": "Temel talimatlar", "codexSet.class.config-toggle": "Buradan değiştirilebilir", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 36d4e6b6d2..825158aaf5 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -1203,6 +1203,7 @@ export const zhTW: Record = { "codexSet.dialog.notRendered": "在我們讀取的那一輪中,此層沒有傳送任何內容。各區段只在內容變更時才會重新傳送,因此單次取樣可能看不到它。", "codexSet.dialog.emptySource": "{path} 檔案存在但為空,因此此層不會傳送任何內容。", "codexSet.dialog.notExposed": "基礎提示詞不在 Codex 可列印的訊息清單中傳遞,因此無法在此顯示。可以透過 model_instructions_file 取代它。", + "codexSet.dialog.unmapped": "此層在提示詞輸出中尚無已確認的對應,因此暫時無法在此顯示其文字。", "codexSet.dialog.textUnavailable": "本機無法讀取 Codex 提示詞,因此無法顯示原文。", "codexSet.class.base": "基礎指令", "codexSet.class.config-toggle": "可在此切換", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 894c9a07ca..e4884030bf 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -962,6 +962,7 @@ export const zh: Record = { "codexSet.dialog.notRendered": "在我们读取的那一轮中,此层没有发送任何内容。各部分仅在内容变化时才会重新发送,因此单次采样可能看不到它。", "codexSet.dialog.emptySource": "{path} 文件存在但为空,因此此层不会发送任何内容。", "codexSet.dialog.notExposed": "基础提示词不在 Codex 可打印的消息列表中传递,因此无法在此显示。可以通过 model_instructions_file 替换它。", + "codexSet.dialog.unmapped": "此层在提示词输出中尚无已确认的映射,因此暂时无法在此显示其文本。", "codexSet.dialog.textUnavailable": "本机无法读取 Codex 提示词,因此无法显示原文。", "codexSet.class.base": "基础指令", "codexSet.class.config-toggle": "可在此切换", diff --git a/gui/tests/codex-set-prompt-layers.test.tsx b/gui/tests/codex-set-prompt-layers.test.tsx index b062e2dc0c..ee939dde4c 100644 --- a/gui/tests/codex-set-prompt-layers.test.tsx +++ b/gui/tests/codex-set-prompt-layers.test.tsx @@ -258,6 +258,21 @@ test("9. the dialog names WHY text is missing rather than omitting it silently", await act(async () => { root.unmount(); }); }); +test("9b. an unmapped layer does not receive the base-prompt explanation", async () => { + stubRoutes(call => call.url.endsWith("/api/codex-prompt/text") + ? json({ ok: true, layers: { personality: { text: null, reason: "unmapped", bytes: 0 } } }) + : json(snapshot())); + const { container, root } = await mount(); + await act(async () => { + (row(container, "personality")!.querySelector("button") as HTMLButtonElement).click(); + }); + const notice = document.querySelector(".codex-set-layer-dialog__no-text")!; + expect(notice.textContent).toContain("no confirmed mapping"); + expect(notice.textContent).not.toContain("base prompt"); + expect(notice.textContent).not.toContain("model_instructions_file"); + await act(async () => { root.unmount(); }); +}); + test("4. a runtime-conditional row states the condition that emits it", async () => { stubRoutes(() => json(snapshot())); const { container, root } = await mount(); diff --git a/src/codex/prompt-text-probe.ts b/src/codex/prompt-text-probe.ts index 6db8d1e49e..b7dfb64de4 100644 --- a/src/codex/prompt-text-probe.ts +++ b/src/codex/prompt-text-probe.ts @@ -73,7 +73,7 @@ export interface LayerText { /** Rendered text, when this layer produced a section on the probed turn. */ text: string | null; /** Why the text is absent, when it is. */ - reason: "ok" | "empty-source" | "not-rendered" | "not-exposed" | "unavailable"; + reason: "ok" | "empty-source" | "not-rendered" | "not-exposed" | "unmapped" | "unavailable"; bytes: number; /** For `empty-source`: the file that exists but has nothing in it. */ sourcePath?: string; @@ -230,9 +230,10 @@ export async function probePromptText(timeoutMs = 15_000): Promise { // Decoding per chunk corrupts UTF-8 that straddles a chunk boundary. expect(probe).toContain("Buffer.concat(chunks).toString(\"utf8\")"); }); + test("27. unmapped layers stay distinct from the unprintable base prompt", async () => { + const probe = await Bun.file(new URL("../src/codex/prompt-text-probe.ts", import.meta.url)).text(); + expect(probe).toContain('layers["base-instructions"] = { text: null, reason: "not-exposed"'); + expect(probe).toContain('layers[id] ??= { text: null, reason: "unmapped"'); + }); test("24. every ownership state is named, not collapsed into a boolean", async () => { // developerInstructionsOwned:false covers an ABSENT key and an EXTERNAL one, and // a GUI that cannot tell them apart hides its own create affordance from every