Project Runner PRP events into task chat

This commit is contained in:
Dotta committed 2026-08-21 08:36:00 -05:00
1 parent 00b9012a28
commit d2c709ed19
6 files changed
+83 -5

No files matched your search

@@ -1316,18 +1316,32 @@ class CodexHarnessSession implements HarnessSession {
if (!isSemanticTool(tool)) {
const admitted = this.#dynamicTools.some((candidate) => candidate.name === tool);
if (admitted && this.#dynamicToolHandler !== undefined) {
this.#emit("item.started", {
kind: "dynamicToolCall",
item: { type: "tool_use", id: callId, name: tool, input: request.params.arguments },
}, { turnId, itemId: callId });
try {
return dynamicToolResponse(await this.#dynamicToolHandler({
const result = await this.#dynamicToolHandler({
tool,
callId,
threadId,
turnId,
arguments: request.params.arguments,
}));
});
this.#emit("item.completed", {
kind: "dynamicToolCall",
item: { type: "tool_result", id: callId, tool_use_id: callId, result },
}, { turnId, itemId: callId });
return dynamicToolResponse(result);
} catch (error) {
const message = boundedText(error instanceof Error ? error.message : error);
this.#emit("item.completed", {
kind: "dynamicToolCall",
item: { type: "tool_result", id: callId, tool_use_id: callId, error: message, is_error: true },
}, { turnId, itemId: callId });
return {
success: false,
contentItems: [{ type: "inputText", text: boundedText(error instanceof Error ? error.message : error) }],
contentItems: [{ type: "inputText", text: message }],
};
}
}
+1
View File
@@ -16171,6 +16171,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
leaseOwner: runOptions.nativeLeaseOwner,
backend: options.nativeSessionBackendFactory?.(nativeExecution),
useRunnerd: agent.adapterType === "paperclip_runner",
onLog,
onSpawn: async (meta) => {
await persistRunProcessMetadata(run.id, meta);
},
@@ -247,6 +247,7 @@ export async function executePaperclipNativeSession(input: {
/** Test seam at the provider boundary; production always uses the package Codex backend. */
backend?: NativeSessionBackend;
useRunnerd?: boolean;
onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
}): Promise<AdapterExecutionResult> {
const leaseOwner = input.leaseOwner ?? `${input.runnerInstanceId}:${randomUUID()}`;
const leaseNow = new Date();
@@ -293,6 +294,10 @@ export async function executePaperclipNativeSession(input: {
completionContractSha256: input.execution.completionContract.sha256,
sourceInstanceId: input.runnerInstanceId,
controlPlaneSourceInstanceId: controlPlaneInstanceId,
}, {
onCommittedEvent: input.onLog
? async (event) => input.onLog!("stdout", `${JSON.stringify({ type: "paperclip.prp.event", event })}\n`)
: undefined,
});
let native: Awaited<ReturnType<typeof executeNativeSession>>;
try {
@@ -66,10 +66,14 @@ export class PaperclipControlPlanePort implements ControlPlanePort {
readonly #db: Db;
readonly #binding: PaperclipControlPlaneBinding;
#sessionId: string | null = null;
readonly #onCommittedEvent?: (event: PrpEvent) => Promise<void>;
constructor(db: Db, binding: PaperclipControlPlaneBinding) {
constructor(db: Db, binding: PaperclipControlPlaneBinding, options: {
onCommittedEvent?: (event: PrpEvent) => Promise<void>;
} = {}) {
this.#db = db;
this.#binding = structuredClone(binding);
this.#onCommittedEvent = options.onCommittedEvent;
}
async openRun(input: OpenControlPlaneRunInput): Promise<void> {
@@ -190,6 +194,7 @@ export class PaperclipControlPlanePort implements ControlPlanePort {
canonicalPayload: event as unknown as Record<string, unknown>,
},
});
if (persisted.disposition === "committed") await this.#onCommittedEvent?.(event);
return {
cursor: persisted.row.seq,
highestContiguousSourceSeq: persisted.highestContiguousSourceSeq,
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { paperclipRunnerUIAdapter } from "./index";
describe("paperclip runner transcript projection", () => {
it("renders committed PRP semantic tool items with the existing chat parts", () => {
const started = paperclipRunnerUIAdapter.parseStdoutLine(JSON.stringify({
type: "paperclip.prp.event",
event: {
eventType: "item.started",
payload: { item: { type: "tool_use", id: "call-1", name: "get_task_context", input: {} } },
},
}), "2026-08-21T12:00:00.000Z");
const completed = paperclipRunnerUIAdapter.parseStdoutLine(JSON.stringify({
type: "paperclip.prp.event",
event: {
eventType: "item.completed",
payload: { item: { type: "tool_result", id: "call-1", tool_use_id: "call-1", result: { ok: true } } },
},
}), "2026-08-21T12:00:01.000Z");
expect(started).toEqual([expect.objectContaining({ kind: "tool_call", name: "get_task_context", toolUseId: "call-1" })]);
expect(completed).toEqual([expect.objectContaining({ kind: "tool_result", toolUseId: "call-1", isError: false })]);
});
});
+31 -1
View File
@@ -2,10 +2,40 @@ import type { UIAdapterModule } from "../types";
import { parseCodexStdoutLine, buildPaperclipRunnerConfig } from "@paperclipai/adapter-codex-local/ui";
import { CodexLocalConfigFields } from "../codex-local/config-fields";
function parsePaperclipRunnerStdoutLine(line: string, ts: string) {
let parsed: unknown;
try {
parsed = JSON.parse(line);
} catch {
return parseCodexStdoutLine(line, ts);
}
if (typeof parsed !== "object" || parsed === null || (parsed as { type?: unknown }).type !== "paperclip.prp.event") {
return parseCodexStdoutLine(line, ts);
}
const event = (parsed as { event?: unknown }).event;
if (typeof event !== "object" || event === null) return [];
const candidate = event as { eventType?: unknown; payload?: unknown };
const eventType = typeof candidate.eventType === "string" ? candidate.eventType : "";
const payload = typeof candidate.payload === "object" && candidate.payload !== null
? candidate.payload as Record<string, unknown>
: {};
if ((eventType === "item.started" || eventType === "item.completed") && payload.item) {
return parseCodexStdoutLine(JSON.stringify({ type: eventType, item: payload.item }), ts);
}
if (eventType === "turn.started") return parseCodexStdoutLine(JSON.stringify({ type: "turn.started" }), ts);
if (eventType === "turn.completed") {
return parseCodexStdoutLine(JSON.stringify({ type: "turn.completed", result: payload.result ?? "" }), ts);
}
if (eventType === "harness.diagnostic") {
return [{ kind: "system" as const, ts, text: String(payload.message ?? payload.code ?? "Runner diagnostic") }];
}
return [];
}
export const paperclipRunnerUIAdapter: UIAdapterModule = {
type: "paperclip_runner",
label: "Paperclip Runner",
parseStdoutLine: parseCodexStdoutLine,
parseStdoutLine: parsePaperclipRunnerStdoutLine,
ConfigFields: CodexLocalConfigFields,
buildAdapterConfig: buildPaperclipRunnerConfig,
};