mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix(runner): record the resumed semantic result
Require the control plane's canonical result receipt and emit it once after durable completion. The live qualifier now reconciles semantic input, result, provider usage, and the final control-plane effect. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7a2a136565
commit
fe3264fb49
3 files changed
+60
-6
No files matched your search
@@ -807,6 +807,22 @@ impl CodexCommandExecutor {
|
||||
.ok_or_else(|| {
|
||||
DurableRunnerError::invalid("semantic_tool.result has no pending call")
|
||||
})?;
|
||||
let result_receipt = result
|
||||
.get("resultReceipt")
|
||||
.filter(|receipt| {
|
||||
receipt.get("schema").and_then(Value::as_str)
|
||||
== Some("paperclip.prp.semantic_tool.v1")
|
||||
&& receipt.get("phase").and_then(Value::as_str) == Some("result")
|
||||
&& receipt.get("callId").and_then(Value::as_str) == Some(call_id)
|
||||
&& receipt.get("operationId").and_then(Value::as_str)
|
||||
== Some(pending.operation_id.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
DurableRunnerError::invalid(
|
||||
"semantic_tool.result requires its matching canonical resultReceipt",
|
||||
)
|
||||
})?;
|
||||
let state = self
|
||||
.state
|
||||
.as_mut()
|
||||
@@ -838,10 +854,17 @@ impl CodexCommandExecutor {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(CommandExecution::result(json!({
|
||||
"status": if delivered { "delivered" } else { "deferred" },
|
||||
"callId": call_id,
|
||||
})))
|
||||
Ok(CommandExecution {
|
||||
result: json!({
|
||||
"status": if delivered { "delivered" } else { "deferred" },
|
||||
"callId": call_id,
|
||||
}),
|
||||
events: vec![(
|
||||
"semantic_tool.result".to_owned(),
|
||||
EventPriority::P0,
|
||||
json!({"semantic_tool": result_receipt}),
|
||||
)],
|
||||
})
|
||||
}
|
||||
|
||||
fn close_session(&mut self) -> Result<CommandExecution, DurableRunnerError> {
|
||||
|
||||
@@ -254,7 +254,34 @@ fn durable_backend_resumes_one_pending_native_tool_call_with_the_same_identity()
|
||||
"semantic_tool.result",
|
||||
json!({
|
||||
"callId": "semantic-call-1",
|
||||
"result": {"ok": true, "value": {"task": "NCV-1"}},
|
||||
"result": {
|
||||
"ok": true,
|
||||
"value": {"task": "NCV-1"},
|
||||
"resultReceipt": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1",
|
||||
"schemaVersion": 1,
|
||||
"phase": "result",
|
||||
"operationId": "report_progress",
|
||||
"callId": "semantic-call-1",
|
||||
"correlation": {
|
||||
"runId": "run-1",
|
||||
"normalizedSessionId": "session-1",
|
||||
"turnId": "turn-1",
|
||||
"itemId": "item-1"
|
||||
},
|
||||
"idempotencyKey": "native-resume-proof-1",
|
||||
"content": {
|
||||
"digest": format!("sha256:{}", "a".repeat(64)),
|
||||
"redactionDisposition": "digest_only",
|
||||
"references": []
|
||||
},
|
||||
"outcome": "succeeded",
|
||||
"code": "ok",
|
||||
"retryable": false,
|
||||
"authorizationBoundary": "active_task",
|
||||
"operationReceiptId": "receipt-1"
|
||||
}
|
||||
},
|
||||
"isError": false,
|
||||
}),
|
||||
))
|
||||
|
||||
@@ -334,6 +334,10 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
|
||||
]);
|
||||
|
||||
if (liveCodex) {
|
||||
const usageReports = nativeEvents
|
||||
.filter((event) => event.eventType === "usage.reported")
|
||||
.map((event) => event.payload?.prpEvent)
|
||||
.filter((event): event is Record<string, unknown> => Boolean(event));
|
||||
console.log("NATIVE_CODEX_RESUME_PROOF", JSON.stringify({
|
||||
appCommit: process.env.PAPERCLIP_LIVE_CODEX_APP_COMMIT ?? null,
|
||||
runId,
|
||||
@@ -349,7 +353,7 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
|
||||
(event) => event.eventType === "semantic_tool.result",
|
||||
).length,
|
||||
controlPlaneEffectCount: progressEffects.length,
|
||||
usage: result.usage ?? null,
|
||||
usageReports,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user