mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
fix(context): hide normalized resume manifests
This commit is contained in:
parent
d74747fbf0
commit
03305a47ef
3 changed files with 61 additions and 5 deletions
|
|
@ -48,6 +48,8 @@ export interface UserMessageContextProjection {
|
|||
manifest: TurnContextManifest | null;
|
||||
}
|
||||
|
||||
type UserMessageContentItem = { type: "text"; text: string } | { type: string };
|
||||
|
||||
export function turnContextManifestText(manifest: TurnContextManifest): string {
|
||||
return `${TURN_CONTEXT_MANIFEST_PREFIX}${TURN_CONTEXT_MANIFEST_NOTICE}${JSON.stringify(manifest)}`;
|
||||
}
|
||||
|
|
@ -110,19 +112,21 @@ function turnContextManifestFromText(text: string): TurnContextManifest | null {
|
|||
}
|
||||
|
||||
export function userMessageContextProjection(
|
||||
content: readonly ({ type: "text"; text: string } | { type: string })[],
|
||||
content: readonly UserMessageContentItem[],
|
||||
clientId: string | null,
|
||||
): UserMessageContextProjection {
|
||||
let manifest: TurnContextManifest | null = null;
|
||||
const visibleText: string[] = [];
|
||||
const lastTextIndex = lastTextItemIndex(content);
|
||||
for (const [index, item] of content.entries()) {
|
||||
if (item.type !== "text" || !("text" in item) || typeof item.text !== "string") continue;
|
||||
const parsed =
|
||||
index > 0 && index === content.length - 1 && item.text.startsWith(`\n${TURN_CONTEXT_MANIFEST_PREFIX}`)
|
||||
? turnContextManifestFromText(item.text)
|
||||
: null;
|
||||
const manifestPrefix = `\n${TURN_CONTEXT_MANIFEST_PREFIX}`;
|
||||
const manifestStart = index === lastTextIndex ? item.text.lastIndexOf(manifestPrefix) : -1;
|
||||
const isStandaloneManifest = manifestStart === 0 && index > 0 && index === content.length - 1;
|
||||
const parsed = manifestStart > 0 || isStandaloneManifest ? turnContextManifestFromText(item.text.slice(manifestStart)) : null;
|
||||
if (parsed && manifestMatchesClientId(parsed, clientId)) {
|
||||
manifest = parsed;
|
||||
if (manifestStart > 0) visibleText.push(item.text.slice(0, manifestStart));
|
||||
continue;
|
||||
}
|
||||
visibleText.push(item.text);
|
||||
|
|
@ -130,6 +134,13 @@ export function userMessageContextProjection(
|
|||
return { text: visibleText.join("\n"), manifest };
|
||||
}
|
||||
|
||||
function lastTextItemIndex(content: readonly UserMessageContentItem[]): number {
|
||||
for (let index = content.length - 1; index >= 0; index -= 1) {
|
||||
if (content[index]?.type === "text") return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function turnContextSubmissionId(value: string): string {
|
||||
const safe = value.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 120);
|
||||
return safe || "context";
|
||||
|
|
|
|||
|
|
@ -205,6 +205,24 @@ describe("turn context manifest trust matching", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("hides a trusted manifest appended to visible text by resume normalization", () => {
|
||||
expect(
|
||||
userMessageContextProjection([{ type: "text", text: `visible request\n${turnContextManifestText(validManifest())}` }], SUBMISSION_ID),
|
||||
).toEqual({
|
||||
text: "visible request",
|
||||
manifest: validManifest(),
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps an appended manifest-like suffix visible when its client ID is not trusted", () => {
|
||||
const text = `visible request\n${turnContextManifestText(validManifest())}`;
|
||||
|
||||
expect(userMessageContextProjection([{ type: "text", text }], "foreign-client")).toEqual({
|
||||
text,
|
||||
manifest: null,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["submission mismatch", { submissionId: "local-user-2-seed-2-2" }],
|
||||
["context mismatch", { contexts: [{ ...validManifest().contexts[0], id: `${SUBMISSION_ID}.99x` }] }],
|
||||
|
|
|
|||
|
|
@ -118,6 +118,33 @@ describe("turn item conversion preserves app-server semantics", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("restores v2 metadata when resume merges text before a non-text input", () => {
|
||||
const clientId = "local-user-1-seed-1-1";
|
||||
const prepared = appServerTurnInputFromCodexInput(
|
||||
[
|
||||
{ type: "text", text: "Read [[Alpha]]." },
|
||||
{ type: "fileReference", name: "Alpha", path: "thoughts/Alpha.md" },
|
||||
{ type: "localImage", path: "/tmp/chart.png" },
|
||||
],
|
||||
clientId,
|
||||
);
|
||||
const normalizedText = prepared.input.flatMap((item) => (item.type === "text" ? [item.text] : [])).join("");
|
||||
const nonTextInput = prepared.input.filter((item) => item.type !== "text");
|
||||
|
||||
expect(
|
||||
threadStreamItemFromTurnItem({
|
||||
type: "userMessage",
|
||||
id: "u1",
|
||||
clientId,
|
||||
content: [{ type: "text", text: normalizedText, text_elements: [] }, ...nonTextInput],
|
||||
}),
|
||||
).toMatchObject({
|
||||
text: "Read [[Alpha]].\n[local image] /tmp/chart.png",
|
||||
copyText: "Read [[Alpha]].\n[local image] /tmp/chart.png",
|
||||
referencedFiles: [{ name: "Alpha", path: "thoughts/Alpha.md" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a persisted attachment descriptor after an unpersisted reference context", () => {
|
||||
const clientId = "local-user-1-seed-1-1";
|
||||
const prepared = appServerTurnInputFromCodexInput(
|
||||
|
|
|
|||
Loading…
Reference in a new issue