fix(dot): synchronize human assignment and preserve Unicode pages

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-07 19:00:52 -05:00
1 parent 155c825315
commit eefd3f22ea
5 files changed
+58 -5

No files matched your search

@@ -26,6 +26,31 @@ describe("Runner workspace bridge", () => {
expect((await readWorkspaceUploadFile(directory, "hello.txt")).toString()).toBe("updated");
await expect(executeWorkspaceTool(directory, "workspace_read", { path: "outside/private.txt" }, authorize)).rejects.toThrow("symlink");
});
it.each(["workspace", "skill"])("preserves Unicode characters across %s text pages", async mode => {
const text = "a".repeat(5999) + "🌍" + "z".repeat(6000) + "🚀";
const directory = await root();
await writeFile(join(directory, "unicode.txt"), text);
const bundle = await materializeAsset([{ path: "SKILL.md", content: Buffer.from(text), mode: 0o444 }]);
const context = { skills: [{ key: "unicode", runtimeName: "unicode", versionId: "version-1", bundle }] } as any;
const read = async (offset: number) => (mode === "workspace"
? executeWorkspaceTool(directory, "workspace_read", { path: "unicode.txt", offset }, authorize)
: readAssignedSkill(context, { skill: "unicode", offset })) as Promise<{ text: string; nextOffset: number | null; sha256: string; byteSize: number }>;
const first = await read(0);
expect(first.nextOffset).toBe(5999);
expect(first.text).toBe("a".repeat(5999));
let combined = first.text, next = first.nextOffset;
while (next !== null) {
const page = await read(next);
expect(page.sha256).toBe(first.sha256);
expect(page.byteSize).toBe(Buffer.byteLength(text));
expect(page.text).not.toMatch(/^[\uDC00-\uDFFF]|[\uD800-\uDBFF]$/u);
combined += page.text;
next = page.nextOffset;
}
expect(combined).toBe(text);
await expect(read(6000)).rejects.toThrow("offset_splits_character");
});
it("serializes overlapping writes so only one observed hash can commit", async () => {
const directory = await root();
const initial = await executeWorkspaceTool(directory, "workspace_write", { path: "shared.txt", text: "initial", expectedSha256: null }, authorize) as any;
@@ -67,10 +67,17 @@ async function readFilePage(file: string, offset: number) {
const stat = await handle.stat();
if (!stat.isFile() || stat.size > 16 * 1024 * 1024) throw new Error("runner_bridge_file_size_limit");
const bytes = await handle.readFile();
// Character pagination avoids splitting UTF-8 sequences; size and hash remain byte-based.
// Offsets remain UTF-16 units, but page boundaries preserve complete code points.
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
const page = text.slice(offset, offset + 6000);
return { text: page, sha256: digest(bytes), byteSize: bytes.length, nextOffset: offset + page.length < text.length ? offset + page.length : null };
const splitsCharacter = (at: number) => {
const before = text.charCodeAt(at - 1), after = text.charCodeAt(at);
return before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff;
};
if (splitsCharacter(offset)) throw new Error("runner_bridge_offset_splits_character");
let end = Math.min(offset + 6000, text.length);
if (splitsCharacter(end)) end--;
const page = text.slice(offset, end);
return { text: page, sha256: digest(bytes), byteSize: bytes.length, nextOffset: end < text.length ? end : null };
} finally { await handle.close(); }
}
export async function readAssignedSkill(context: NativeRuntimeContextSnapshot, raw: unknown) {