From a97649f28307a5a2576cfd2c58f0cc54e11dbd5d Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 5 Oct 2026 17:15:37 -0500 Subject: [PATCH] Match Pi native question schemas to accepted method fields --- .../2026-10-02-pi-production-readiness.md | 9 ++++++++ .../drivers/acpx/pi-runtime-extension.test.ts | 21 +++++++++++++++++++ .../src/drivers/acpx/pi-runtime-extension.ts | 20 +++++++++++++----- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/doc/plans/2026-10-02-pi-production-readiness.md b/doc/plans/2026-10-02-pi-production-readiness.md index 266665746d5..9ffab2a17b0 100644 --- a/doc/plans/2026-10-02-pi-production-readiness.md +++ b/doc/plans/2026-10-02-pi-production-readiness.md @@ -36,6 +36,15 @@ Cleanup passes. Both failed corrected local attempts remain failed; the memory case is not qualified. No byte assertion, timeout, model or permission boundary is relaxed. Production remains held; no merge or release is authorized. +The corrected Linux native-questions attempt delivers the first three typed +answers, then its editor call fails with `Pi question has unsupported fields`. +The original schema advertises fields that the selected method cannot accept. +The schema correction separates the four method contracts while retaining +strict handler validation. All 37 Pi extension tests pass. A credential-free +calibration proves that the installed old schema accepts an editor placeholder +that its handler rejects, while the corrected schema rejects that input before +execution. This correction also requires fresh runtime artifact qualification. + ## Frozen target - Pi: `@earendil-works/pi-coding-agent@1.0.0`. diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts index c174be6d761..eed8c692b9b 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.test.ts @@ -4,6 +4,7 @@ import { mkdtemp, mkdir, realpath, rename, rm, symlink, writeFile } from "node:f import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; +import Ajv from "ajv"; import { checkPiNativeTool, installPiRuntimeExtension, piMcpRequest, readPiRuntimeConfiguration, PI_NATIVE_QUESTION_TOOL, PI_PERMISSION_TITLE_PREFIX, type PiExtensionApi, type PiRuntimeConfiguration, type PiToolDefinition, @@ -30,6 +31,26 @@ function harness() { } describe("owned Pi runtime extension", () => { + it("advertises only the fields each native question method accepts", async () => { + const { config } = await workspace(); const h = harness(); + await installPiRuntimeExtension(h.api, config); + const validate = new Ajv().compile(h.nativeTools[0]!.parameters); + const questions = [ + { method: "select", title: "Color", options: [{ id: "blue", label: "Blue" }] }, + { method: "confirm", title: "Preference", message: "Prefer dark mode?" }, + { method: "input", title: "Name", placeholder: "Name" }, + { method: "editor", title: "Draft", prefill: "Old draft" }, + ]; + for (const question of questions) expect(validate(question)).toBe(true); + for (const question of [ + { ...questions[3], placeholder: "Name" }, + { ...questions[2], prefill: "Old draft" }, + { ...questions[1], options: [] }, + { method: "select", title: "Color" }, + { method: "confirm", title: "Preference" }, + ]) expect(validate(question)).toBe(false); + }); + it("stops Pi 1 cache warming even when native economics recommend a paid refresh", async () => { const { config } = await workspace(); const h = harness(); await installPiRuntimeExtension(h.api, config); diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts index 80463094375..df4fe1a59a8 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts @@ -374,11 +374,21 @@ export async function installPiRuntimeExtension( pi.registerTool({ name: PI_NATIVE_QUESTION_TOOL, label: "Ask a native question", description: "Ask the human a native Pi select, confirm, input, or editor question. Select is single-choice and returns the supplied stable option ID. This cannot approve tools or a Paperclip Plan. For durable task questions or Plan approval use the assigned Paperclip semantic tools. Cancellation is not an answer; confirm false means No or dismissal.", - parameters: { type: "object", additionalProperties: false, required: ["method", "title"], properties: { - method: { type: "string", enum: ["select", "confirm", "input", "editor"] }, title: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH }, - options: { type: "array", minItems: 1, maxItems: 128, items: { type: "object", additionalProperties: false, required: ["id", "label"], properties: { id: { type: "string", pattern: "^[A-Za-z0-9_-]{1,128}$" }, label: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH } } } }, - message: { type: "string", maxLength: 16384 }, placeholder: { type: "string", maxLength: 16384 }, prefill: { type: "string", maxLength: 16384 }, - } }, + parameters: { type: "object", anyOf: [ + { type: "object", additionalProperties: false, required: ["method", "title", "options"], properties: { + method: { const: "select" }, title: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH }, + options: { type: "array", minItems: 1, maxItems: 128, items: { type: "object", additionalProperties: false, required: ["id", "label"], properties: { id: { type: "string", pattern: "^[A-Za-z0-9_-]{1,128}$" }, label: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH } } } }, + } }, + { type: "object", additionalProperties: false, required: ["method", "title", "message"], properties: { + method: { const: "confirm" }, title: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH }, message: { type: "string", maxLength: 16384 }, + } }, + { type: "object", additionalProperties: false, required: ["method", "title"], properties: { + method: { const: "input" }, title: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH }, placeholder: { type: "string", maxLength: 16384 }, + } }, + { type: "object", additionalProperties: false, required: ["method", "title"], properties: { + method: { const: "editor" }, title: { type: "string", maxLength: PI_QUESTION_LABEL_MAX_LENGTH }, prefill: { type: "string", maxLength: 16384 }, + } }, + ] }, async execute(callId, args, signal, _onUpdate, context) { const id = identities.bind(callId, PI_NATIVE_QUESTION_TOOL, args); const previous = questions.get(id); if (previous) return previous;