Merge branch 'codex/runner-cursor-acp' into codex/runner-copilot-acp

* codex/runner-cursor-acp:
  Bound Cursor child summaries without splitting Unicode characters

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-29 11:49:53 -05:00
commit df3c87735a
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. */