mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
35 lines
3.1 KiB
TypeScript
35 lines
3.1 KiB
TypeScript
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
|
|
import { pollUntil } from "../api.js";
|
|
import { observeInternal, observation, type Entity, type InternalObservation } from "./internal-support.js";
|
|
|
|
/** These cases exercise only the launcher-owned instance or a rendered developer fixture. */
|
|
export function privilegedDefinition(scenarioId: string, module: string, extra: Pick<ChatCatalogCase, "setup" | "grade" | "act" | "verifyResult">): ChatCatalogCase {
|
|
return { scenarioId, oracleVersion: "paperclip.chat-catalog.privileged.v1", coverageGaps: [],
|
|
authentication: "human-session", expectedRunCount: 1, turnTimeoutMs: 120_000, attemptTimeoutMs: 420_000,
|
|
sourceFiles: ["chat-catalog-domains/privileged-support.ts", `chat-catalog-domains/${module}.ts`, `chat-scenarios/${scenarioId.split(".")[0]}.yaml`],
|
|
observe: observeInternal, ...extra };
|
|
}
|
|
export async function settlePrivilegedChat(ctx: ChatCatalogContext): Promise<InternalObservation> {
|
|
return pollUntil({ label: "Canonical privileged guidance settled", deadlineAt: Date.now() + 150_000,
|
|
load: () => observeInternal(ctx, {}),
|
|
accept: o => o.runs.length === 1 && o.runs[0]?.status === "succeeded" && !!o.reply && ctx.issue()?.conversationState === "waiting",
|
|
reject: o => o.runs.some(run => ["failed", "timed_out", "cancelled"].includes(run.status)) ? "Canonical guidance run failed" : undefined });
|
|
}
|
|
export const privilegedObservation = observation;
|
|
|
|
/** Bind a declared factual operator handoff to its actual persisted human message and native reply. */
|
|
export async function companionPhase(ctx: ChatCatalogContext, priorRunIds: string[], message: string) {
|
|
const final = await pollUntil({ label: "Native reply to the actual operator receipt", deadlineAt: Date.now() + 150_000,
|
|
load: async () => { await ctx.refreshIssue(); return { issue: ctx.issue(), runs: await ctx.allRuns() }; },
|
|
accept: o => o.issue?.conversationState === "waiting" && o.runs.length === priorRunIds.length + 1 && o.runs.every(run => run.status === "succeeded") });
|
|
const run = final.runs.find(run => !priorRunIds.includes(run.id) && run.agentId === ctx.input.fixtures.agent.id);
|
|
if (!run) throw new Error("Operator receipt did not produce its own native reply");
|
|
const comments = await ctx.input.api.get<Entity[]>(`/api/issues/${ctx.issue()!.id}/comments?order=asc`);
|
|
const human = comments.filter(comment => !comment.authorAgentId && comment.body === message);
|
|
const replies = comments.filter(comment => comment.authorAgentId === ctx.input.fixtures.agent.id && comment.createdByRunId === run.id);
|
|
const profile = await ctx.input.api.get<{ runnerProfileJson?: { nativeExecutionInput?: { task?: { prompt?: string } } } }>(`/api/heartbeat-runs/${run.id}`);
|
|
return { humanCommentId: human[0]?.id, runId: run.id, reply: replies.map(row => row.body).join("\n"),
|
|
attributed: human.length === 1 && replies.length > 0 && profile.runnerProfileJson?.nativeExecutionInput?.task?.prompt?.includes(human[0]!.id) === true
|
|
&& replies.every(reply => Date.parse(reply.createdAt!) >= Date.parse(human[0]!.createdAt!)) };
|
|
}
|