mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-10 12:07:09 +02:00
Bound Cursor child summaries without splitting Unicode characters
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
62e642590f
commit
76d943e625
2 files changed
+28
-1
No files matched your search
@@ -2,6 +2,7 @@ import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { validateAcpxRichEvent } from "./profile-extensions.js";
|
||||
import { PAPERCLIP_QUESTION_RESPONSE_SCHEMA } from "../../contracts/question-set.js";
|
||||
import {
|
||||
createCursorNotificationNormalizer, createCursorProfileExtensionAdapter, createCursorSubagentNormalizer, cursorWorkspaceArtifactReference,
|
||||
@@ -144,6 +145,20 @@ describe("Cursor active-turn extension adapter", () => {
|
||||
await expect(createCursorProfileExtensionAdapter({ ...context, turnId: "next" }).notification("cursor/subagent_update", { sessionId: "parent", update: state })).rejects.toThrow("no spawn");
|
||||
});
|
||||
|
||||
it.each(["😀".repeat(2_500), "😀".repeat(5_000), "漢😀".repeat(2_000)])("bounds Unicode child activity for UTF-16 consumers without splitting code points (%#)", async message => {
|
||||
const adapter = createCursorProfileExtensionAdapter({ workspacePath: await workspace(), sessionId: "parent", turnId: "turn" });
|
||||
await adapter.notification("cursor/subagent_update", { sessionId: "parent", update: { sessionUpdate: "subagent_spawned", subagentSessionId: "child", name: "Explore", task: "", _meta: { cursor: { toolCallId: "task", agentId: "agent" } } } });
|
||||
const events = await adapter.notification("cursor/subagent_update", { sessionId: "parent", childSessionId: "child", update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: message } } });
|
||||
const event = events[0]!;
|
||||
expect(() => validateAcpxRichEvent(event)).not.toThrow();
|
||||
const summary = (event.payload.children as { activitySummary: string }[])[0]!.activitySummary;
|
||||
expect(summary.length).toBeLessThanOrEqual(4_000);
|
||||
expect(summary.isWellFormed()).toBe(true);
|
||||
expect(summary).toMatch(/^\[Earlier child activity omitted\]\n/);
|
||||
const tail = summary.slice("[Earlier child activity omitted]\n".length);
|
||||
expect(message.endsWith(tail)).toBe(true);
|
||||
});
|
||||
|
||||
it("attributes nested child transcripts, reports partial tool detail, and settles disconnected children honestly", async () => {
|
||||
const adapter = createCursorProfileExtensionAdapter({ workspacePath: await workspace(), sessionId: "parent", turnId: "turn" });
|
||||
const child = { sessionUpdate: "subagent_spawned", subagentSessionId: "child", name: "Explore", task: "", _meta: { cursor: { toolCallId: "task", agentId: "agent" } } };
|
||||
|
||||
@@ -369,8 +369,20 @@ function cursorChildActivitySummary(update: Record<string, unknown>): { text: st
|
||||
}
|
||||
|
||||
function boundedChildActivity(text: string): string {
|
||||
if (text.length <= 4_000) return text;
|
||||
const prefix = "[Earlier child activity omitted]\n";
|
||||
const points = Array.from(text);
|
||||
return points.length <= 4_000 ? text : `[Earlier child activity omitted]\n${points.slice(-3_960).join("")}`;
|
||||
const tail: string[] = [];
|
||||
let length = prefix.length;
|
||||
// Bound both UTF-16 consumers and the schema's Unicode code-point count.
|
||||
// Keep each complete code point when retaining the most recent activity.
|
||||
for (let index = points.length - 1; index >= 0; index -= 1) {
|
||||
const point = points[index]!;
|
||||
if (length + point.length > 4_000) break;
|
||||
tail.push(point);
|
||||
length += point.length;
|
||||
}
|
||||
return prefix + tail.reverse().join("");
|
||||
}
|
||||
|
||||
/** Preserve identity across delta state updates; use a fresh reducer per turn. */
|
||||
|
||||
Reference in new issue
Block a user