mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Merge branch 'codex/work-folders-staging-hardening-refresh' into codex/work-folders-remote-recovery-refresh
* codex/work-folders-staging-hardening-refresh: fix(runner): preserve exact Claude model selectors during ACP admission test(runner): serve goal reads while holding passive completion notices fix(ci): validate shared Cloud image contexts throughout the stack
This commit is contained in:
commit
3aebfbaa14
3 files changed
+77
-51
No files matched your search
+63
-48
@@ -1619,56 +1619,71 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}))?;
|
||||
}
|
||||
if emit_post_completion_passive_statuses {
|
||||
if let Some(gate) = post_completion_notification_gate.as_ref() {
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !gate.is_file() {
|
||||
if std::time::Instant::now() >= deadline {
|
||||
return Err(
|
||||
"post-completion notification gate timed out".into()
|
||||
);
|
||||
let gate = post_completion_notification_gate.clone();
|
||||
let thread_id = state.thread_id.clone();
|
||||
let tail_turn_id = provider_turn_id.clone();
|
||||
// Goal reconciliation reads must remain responsive while
|
||||
// the test holds back the post-terminal passive notices.
|
||||
thread::spawn(move || {
|
||||
let result = (|| -> io::Result<()> {
|
||||
if let Some(gate) = gate.as_ref() {
|
||||
let deadline =
|
||||
std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !gate.is_file() {
|
||||
if std::time::Instant::now() >= deadline {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"post-completion notification gate timed out",
|
||||
));
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
for notification in [
|
||||
json!({
|
||||
"method": "deprecationNotice",
|
||||
"params": {"summary": "A provider setting is deprecated", "details": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "remoteControl/status/changed",
|
||||
"params": {"status": "disabled", "environmentId": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "mcpServer/startupStatus/updated",
|
||||
"params": {"name": "codex_apps", "status": "ready", "error": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "account/rateLimits/updated",
|
||||
"params": {"rateLimits": {}}
|
||||
}),
|
||||
json!({
|
||||
"method": "rawResponseItem/completed",
|
||||
"params": {"threadId": thread_id, "turnId": tail_turn_id, "item": {"id": "raw-tail", "type": "reasoning"}}
|
||||
}),
|
||||
json!({
|
||||
"method": "rawResponse/completed",
|
||||
"params": {"threadId": thread_id, "turnId": tail_turn_id, "response": {"id": "response-tail"}}
|
||||
}),
|
||||
json!({
|
||||
"method": "thread/goal/updated",
|
||||
"params": {"threadId": thread_id, "goal": "finish the turn"}
|
||||
}),
|
||||
json!({
|
||||
"method": "thread/goal/cleared",
|
||||
"params": {"threadId": thread_id}
|
||||
}),
|
||||
] {
|
||||
send(notification)?;
|
||||
}
|
||||
if let Some(gate) = gate.as_ref() {
|
||||
fs::write(gate.with_extension("emitted"), b"emitted")?;
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
if let Err(error) = result {
|
||||
eprintln!("post-completion passive tail failed: {error}");
|
||||
}
|
||||
}
|
||||
for notification in [
|
||||
json!({
|
||||
"method": "deprecationNotice",
|
||||
"params": {"summary": "A provider setting is deprecated", "details": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "remoteControl/status/changed",
|
||||
"params": {"status": "disabled", "environmentId": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "mcpServer/startupStatus/updated",
|
||||
"params": {"name": "codex_apps", "status": "ready", "error": null}
|
||||
}),
|
||||
json!({
|
||||
"method": "account/rateLimits/updated",
|
||||
"params": {"rateLimits": {}}
|
||||
}),
|
||||
json!({
|
||||
"method": "rawResponseItem/completed",
|
||||
"params": {"threadId": state.thread_id, "turnId": provider_turn_id, "item": {"id": "raw-tail", "type": "reasoning"}}
|
||||
}),
|
||||
json!({
|
||||
"method": "rawResponse/completed",
|
||||
"params": {"threadId": state.thread_id, "turnId": provider_turn_id, "response": {"id": "response-tail"}}
|
||||
}),
|
||||
json!({
|
||||
"method": "thread/goal/updated",
|
||||
"params": {"threadId": state.thread_id, "goal": "finish the turn"}
|
||||
}),
|
||||
json!({
|
||||
"method": "thread/goal/cleared",
|
||||
"params": {"threadId": state.thread_id}
|
||||
}),
|
||||
] {
|
||||
send(notification)?;
|
||||
}
|
||||
if let Some(gate) = post_completion_notification_gate.as_ref() {
|
||||
fs::write(gate.with_extension("emitted"), b"emitted")?;
|
||||
}
|
||||
});
|
||||
}
|
||||
if emit_post_completion_foreign_turn {
|
||||
let gate = post_completion_notification_gate.clone();
|
||||
|
||||
@@ -101,7 +101,10 @@ describe("Codex ACPX runtime adapter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([["claude" as const, "claude-sonnet-5", "claude-sonnet-5"]])(
|
||||
it.each([
|
||||
["claude" as const, "claude-sonnet-5", "claude-sonnet-5"],
|
||||
["claude" as const, "custom-claude-model", "custom-claude-model"],
|
||||
])(
|
||||
"opens the qualified %s session through the verified lease",
|
||||
async (agent, model, providerModel) => {
|
||||
const runtime = fakeRuntime();
|
||||
@@ -109,7 +112,7 @@ describe("Codex ACPX runtime adapter", () => {
|
||||
const options = openOptions(command);
|
||||
let runtimeOptions: AcpRuntimeOptions | undefined;
|
||||
options.profile = resolveQualifiedAcpxProfile(agent, model);
|
||||
options.launchEnvironment = { PATH: "/verified/bin" };
|
||||
options.launchEnvironment = { PATH: "/verified/bin", ANTHROPIC_CUSTOM_MODEL_OPTION: "stale-model" };
|
||||
|
||||
await openCodexAcpxRuntime(options, {
|
||||
createRegistry: ({ overrides }) => {
|
||||
@@ -128,6 +131,7 @@ describe("Codex ACPX runtime adapter", () => {
|
||||
expect(runtimeOptions?.spawnEnvironment?.()).toEqual({
|
||||
PATH: "/verified/bin",
|
||||
PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1",
|
||||
ANTHROPIC_CUSTOM_MODEL_OPTION: providerModel,
|
||||
});
|
||||
expect(runtime.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -317,7 +317,14 @@ export async function openQualifiedAcpxRuntime(
|
||||
spawnEnvironment: () => ({
|
||||
...definedEnvironment(options.launchEnvironment),
|
||||
...(options.profile.agent === "claude"
|
||||
? { PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1" }
|
||||
? {
|
||||
PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1",
|
||||
// Claude ACP otherwise maps concrete IDs back to rolling picker
|
||||
// aliases (e.g. claude-sonnet-5 -> sonnet). Advertise the exact
|
||||
// host-requested ID so model verification stays exact on resume
|
||||
// and before the first billable prompt.
|
||||
ANTHROPIC_CUSTOM_MODEL_OPTION: options.profile.reportedModelId,
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
spawnCwd: options.cwd,
|
||||
|
||||
Reference in new issue
Block a user