mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-12 18:06:11 +02:00
fix(dot): synchronize human assignment and preserve Unicode pages
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
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) {
|
||||
|
||||
Reference in new issue
Block a user