mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 02:26:12 +02:00
test: require recalled chat context after server restart
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
fd20a94ce6
commit
c04324fab1
5 files changed
+24
-5
No files matched your search
@@ -168,6 +168,10 @@ lifecycle/performance events are insufficient. The startup case must stop after
|
||||
a process launch request but before a provider turn starts. Missing the boundary
|
||||
fails the case instead of silently testing another phase.
|
||||
|
||||
Restart continuity requires the agent to recall a phrase after the server
|
||||
restarts. The final prompt does not reveal that phrase. A generic successful
|
||||
reply after restart cannot pass this check.
|
||||
|
||||
The committed-send case withholds the browser's comment acknowledgement until
|
||||
the agent has saved one backlog task, drops that response, restarts Paperclip,
|
||||
and replays the exact public request with the original client request ID. It
|
||||
|
||||
@@ -948,7 +948,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
|
||||
profiles: runnerProfiles.filter(profile => ["legacy-codex", "legacy-claude", "runner-codex", "runner-acpx-claude"].includes(profile.id)).map(defaultPermissionProfile),
|
||||
environments: [localEnvironment], tasks: chatTasks, expectedMatrixSize: 28,
|
||||
excludedExecutionIds: ["legacy-codex", "legacy-claude"].flatMap(profile => ["reassign-task", "create-backlog"].map(task => `agent-chat.${profile}.local.${task}`)),
|
||||
definitionMetadata: { version: 5, resetRunsCountedSeparately: true, permissions: "production-defaults", stopBoundary: "provider-turn-started" },
|
||||
definitionMetadata: { version: 6, resetRunsCountedSeparately: true, permissions: "production-defaults", stopBoundary: "provider-turn-started", restartMemory: "required-after-restart" },
|
||||
},
|
||||
{
|
||||
id: "agent-chat-hardening", label: "Agent Chat Recovery and Coordination", manualOnly: true,
|
||||
@@ -959,7 +959,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
|
||||
environments: [localEnvironment, daytonaWarmEnvironment], tasks: chatHardeningTasks, expectedMatrixSize: 18,
|
||||
excludedExecutionIds: ["runner-codex", "runner-acpx-claude"].flatMap(profile =>
|
||||
["stop-startup-new-resume", "hire-delegate-reuse", "blocked-status-review"].map(task => `agent-chat-hardening.${profile}.daytona.${task}`)),
|
||||
definitionMetadata: { version: 1, permissions: "production-defaults", instructions: "production", grading: "durable-state-and-source-evidence", scheduling: "explicit-only" },
|
||||
definitionMetadata: { version: 2, permissions: "production-defaults", instructions: "production", grading: "durable-state-and-source-evidence", scheduling: "explicit-only", restartMemory: "required-after-restart" },
|
||||
},
|
||||
...(process.env.PAPERCLIP_RUNNER_E2E_CONNECTION_REVIEWS === "1" ? [connectionReviewSuite] : []),
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { LiveFixtureValues } from "./live-fixtures.js";
|
||||
import type { MatrixExecution } from "./types.js";
|
||||
import { isBlockedUnstartedWake } from "./non-execution-wake.js";
|
||||
import { chatMarker } from "./chat-cases.js";
|
||||
import { assertChatStartupStopped, isChatStopReady, runChatHardeningFlow } from "./chat-hardening.js";
|
||||
import { assertChatRememberedAfterRestart, assertChatStartupStopped, isChatStopReady, runChatHardeningFlow } from "./chat-hardening.js";
|
||||
|
||||
// Public API observations only: this driver never fabricates provider results or writes DB state.
|
||||
export interface ChatIssue {
|
||||
@@ -409,9 +409,14 @@ export async function runChatFlow(input: ChatFlowInput) {
|
||||
await idle(2);
|
||||
expect(runs).toHaveLength(count);
|
||||
await turn(
|
||||
`We are done discussing it. Reply with ${marker} only; no further work.`,
|
||||
`What phrase did I ask you to remember earlier? Reply with that remembered phrase followed by ${marker}; no further work.`,
|
||||
3,
|
||||
);
|
||||
assertChatRememberedAfterRestart(
|
||||
(await comments()).filter((comment) => comment.authorAgentId).at(-1)?.body ?? "",
|
||||
secret,
|
||||
marker,
|
||||
);
|
||||
} else {
|
||||
let cancelledId: string | undefined;
|
||||
if (stopCase) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { assertChatHire, assertChatSourceReview, assertChatStartupStopped, assertCommittedSendRetry, assertGroundedChatStatus, isChatStopReady } from "./chat-hardening.js";
|
||||
import { assertChatHire, assertChatRememberedAfterRestart, assertChatSourceReview, assertChatStartupStopped, assertCommittedSendRetry, assertGroundedChatStatus, isChatStopReady } from "./chat-hardening.js";
|
||||
import { runnerMatrix } from "./catalog.js";
|
||||
import { buildRunnerE2EProcessEnvironment } from "./harness-env.js";
|
||||
|
||||
@@ -10,6 +10,11 @@ const run = { id: "run", companyId: "company", agentId: "hire", status: "succeed
|
||||
const hiring = { agents: [{ id: "lead", name: "Lead", adapterConfig: { model: "model" } }, hire], leadId: "lead", hireName: "Morgan", hiredId: "hire", connectionId: "account", binding, taskIds: ["task"], tasks: [task], runs: [run] };
|
||||
|
||||
describe("agent chat hardening oracles", () => {
|
||||
it("requires remembered context after restart, not just a new reply", () => {
|
||||
expect(() => assertChatRememberedAfterRestart("OLDCONTEXT123 CHAT123", "OLDCONTEXT123", "CHAT123")).not.toThrow();
|
||||
expect(() => assertChatRememberedAfterRestart("CHAT123", "OLDCONTEXT123", "CHAT123")).toThrow();
|
||||
expect(() => assertChatRememberedAfterRestart("OLDCONTEXT999 CHAT123", "OLDCONTEXT123", "CHAT123")).toThrow();
|
||||
});
|
||||
it("grades actual execution by the original hired identity and managed account", () => {
|
||||
expect(() => assertChatHire(hiring)).not.toThrow();
|
||||
for (const wrong of [
|
||||
|
||||
@@ -76,6 +76,11 @@ export function isChatStopReady(events: Array<{ eventType?: unknown }>, phase: "
|
||||
return phase === "active" ? active : !active && events.some(event => event.eventType === "native.process_start_requested");
|
||||
}
|
||||
|
||||
export function assertChatRememberedAfterRestart(reply: string, remembered: string, marker: string) {
|
||||
expect(reply).toContain(remembered);
|
||||
expect(reply).toContain(marker);
|
||||
}
|
||||
|
||||
export function assertChatStartupStopped(run: ChatRun, events: Array<{ eventType?: unknown; createdAt?: string }>) {
|
||||
const cancellation = run.resultJson?.nativeCancellation as Record<string, unknown> | undefined;
|
||||
const started = events.filter(event => ["turn.started", "turn.accepted"].includes(String(event.eventType)));
|
||||
|
||||
Reference in new issue
Block a user