fix(native): retain actionable final replies and document links

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-04 20:27:53 -05:00
1 parent 3737f89626
commit be604fcbf8
15 files changed
+195 -12

No files matched your search

@@ -158,16 +158,18 @@ afterAll(() => {
const output = process.env.PAPERCLIP_NATIVE_INSTRUCTION_REPORT;
if (!output) return;
if (receipts.length !== 18) throw new Error("Incomplete native instruction measurement; refusing a partial receipt");
const sourcePaths = ["runtime-context.ts", "codex-native-backend.ts", "opencode-native-backend.ts"];
const source = inspectNativeCompletionSourceMetadata({ repositoryRoot: new URL("../../../../", import.meta.url).pathname,
sourceFiles: sourcePaths.map(file => `packages/paperclip-runner/src/backends/${file}`),
const repositoryRoot = new URL("../../../../", import.meta.url);
const sourcePaths = ["runtime-context.ts", "codex-native-backend.ts", "opencode-native-backend.ts"].map(file => `packages/paperclip-runner/src/backends/${file}`);
sourcePaths.push("server/src/services/native-runtime/paperclip-runner-tool-authority.ts");
const source = inspectNativeCompletionSourceMetadata({ repositoryRoot: repositoryRoot.pathname,
sourceFiles: sourcePaths,
baseSha: "2a8a99e4a5f69aa803b3f10b982f583e75a87042", variant: "measurement" });
writeFileSync(output, `${JSON.stringify({
schema: "paperclip.native-instruction-measurement.v1",
sourceSha: execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim(),
sourceDirty: !source.immutable,
sourceMetadata: source.sourceMetadata,
sourceHashes: Object.fromEntries(sourcePaths.map(file => [file, sha256(readFileSync(new URL(file, import.meta.url)))])),
sourceHashes: Object.fromEntries(sourcePaths.map(file => [file.split("/").at(-1)!, sha256(readFileSync(new URL(file, repositoryRoot)))])),
fixtureSha256: sha256(readFileSync(new URL(import.meta.url))),
boundary: "scripted runnerd RPC; complete Paperclip instructions, fixture core tool schemas and turn input",
providerCalls: 0, tokenCount: null, vendorStockPrompt: "unavailable", modelBehavior: "not_measured",
@@ -68,6 +68,7 @@ describe("native runtime context files", () => {
"Obtain one accepted result from paperclip_finish or paperclip_block before writing",
);
expect(constraints).toContain("before writing the complete user-facing final response.");
expect(constraints).toContain("If blocked, explain why work cannot continue, name the owner and give the unblock action.");
for (const description of [PRP_COMPLETION_TOOL_DESCRIPTION, PRP_BLOCK_TOOL_DESCRIPTION]) {
expect(description).toContain("If rejected, correct the report and retry.");
expect(description).toContain("After acceptance, read the returned outcome");
@@ -89,6 +90,7 @@ describe("native runtime context files", () => {
expect(constraints).toContain("register_deliverable");
expect(constraints).toContain("deliverable:");
expect(constraints).toContain("download link");
expect(constraints).toContain("returned documentHref as a clickable link in your final response");
});
it("marks only authoritative answered-question envelopes as resolved in the outer task", () => {
@@ -34,7 +34,7 @@ export function nativeTaskConstraints(input: NativeExecutionInput): string[] {
// Keep the discovery/ordering rule in each turn. The completion tools own
// reporting, rejection feedback, approval handling and final-response details.
const finalResponseConstraint =
"Obtain one accepted result from paperclip_finish or paperclip_block before writing the complete user-facing final response. Follow that tool's reporting and final-response instructions.";
"Obtain one accepted result from paperclip_finish or paperclip_block before writing the complete user-facing final response. Follow that tool's reporting and final-response instructions. If blocked, explain why work cannot continue, name the owner and give the unblock action.";
const answeredQuestions = Array.isArray(input.interactionResponses)
? input.interactionResponses.flatMap((response, responseIndex) => {
if (
@@ -112,7 +112,7 @@ export function nativeTaskConstraints(input: NativeExecutionInput): string[] {
: `For this turn, the editable agent instruction file is ${input.runtimeContext.instructions.workingCopy.rootPath}/${input.runtimeContext.instructions.workingCopy.entryPath}. This replaces any private working-copy path from a previous turn. Ordinary edits save after the provider stops and only with a durable revision receipt. Use the agent instruction tools for immediate saves. Shared instruction assets and repository instructions are not collected.`,
] : []),
"Use Paperclip semantic tools for coordination and finalization.",
"Save requested plans and Paperclip documents directly with write_document. A saved Paperclip document is already a durable deliverable. Do not create a local file, compute file hashes, or call register_deliverable for it unless the user also requests a downloadable file. Cite the saved document in your completion evidence and final response.",
"Save requested plans and Paperclip documents directly with write_document. A saved Paperclip document is already a durable deliverable. Do not create a local file, compute file hashes, or call register_deliverable for it unless the user also requests a downloadable file. Cite the saved document in your completion evidence and include its returned documentHref as a clickable link in your final response.",
"When the requested result is a file, use register_deliverable before paperclip_finish. Compute its exact byte size and SHA-256, register the workspace-relative file, cite deliverable:<attachmentId> from the receipt as completion evidence, and include /api/attachments/<attachmentId>/content as the download link in your answer. A bare workspace filename is not a delivered result. For repository edits, cite an accessible PR or registered work product. Preserve existing work; do not upload unrelated files. If file publication fails, fix it or report the concrete blocker instead of claiming the file is delivered.",
...(answeredQuestionConstraint ? [answeredQuestionConstraint] : []),
finalResponseConstraint,