feat: decline pending action
This commit is contained in:
@@ -61,6 +61,19 @@ async def test_draft_message_creates_pending_action():
|
|||||||
assert pending["id"] in registry.pending_actions
|
assert pending["id"] in registry.pending_actions
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_decline_pending_action_removes_without_sending():
|
||||||
|
registry = ToolRegistry(FakeUEX())
|
||||||
|
result = await registry.draft_negotiation_message(hash="abc", message="Would you take 4500 UEC?")
|
||||||
|
action_id = result["pending_action"]["id"]
|
||||||
|
|
||||||
|
declined = await registry.decline(action_id)
|
||||||
|
|
||||||
|
assert declined["declined"] is True
|
||||||
|
assert declined["pending_action"]["id"] == action_id
|
||||||
|
assert action_id not in registry.pending_actions
|
||||||
|
|
||||||
|
|
||||||
def test_uex_client_uses_bearer_and_secret_headers():
|
def test_uex_client_uses_bearer_and_secret_headers():
|
||||||
client = UEXClient("https://api.uexcorp.space/2.0", secret_key="secret", bearer_token="bearer")
|
client = UEXClient("https://api.uexcorp.space/2.0", secret_key="secret", bearer_token="bearer")
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ def create_app() -> FastAPI:
|
|||||||
async def approve(action_id: str) -> dict:
|
async def approve(action_id: str) -> dict:
|
||||||
return await tools.approve(action_id)
|
return await tools.approve(action_id)
|
||||||
|
|
||||||
|
@app.post("/api/decline/{action_id}")
|
||||||
|
async def decline(action_id: str) -> dict:
|
||||||
|
return await tools.decline(action_id)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -241,6 +241,20 @@ class ToolRegistry:
|
|||||||
return {"error": f"Pending action not found: {action_id}"}
|
return {"error": f"Pending action not found: {action_id}"}
|
||||||
return await self.uex.post(action.endpoint, action.payload, authenticated=True)
|
return await self.uex.post(action.endpoint, action.payload, authenticated=True)
|
||||||
|
|
||||||
|
async def decline(self, action_id: str) -> dict[str, Any]:
|
||||||
|
action = self.pending_actions.pop(action_id, None)
|
||||||
|
if not action:
|
||||||
|
return {"error": f"Pending action not found: {action_id}"}
|
||||||
|
return {
|
||||||
|
"declined": True,
|
||||||
|
"pending_action": {
|
||||||
|
"id": action.id,
|
||||||
|
"label": action.label,
|
||||||
|
"endpoint": action.endpoint,
|
||||||
|
"payload": action.payload,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
async def search_marketplace_listings(
|
async def search_marketplace_listings(
|
||||||
self,
|
self,
|
||||||
query: str | None = None,
|
query: str | None = None,
|
||||||
|
|||||||
+26
-1
@@ -299,7 +299,14 @@ function renderPending(actions) {
|
|||||||
const approve = document.createElement("button");
|
const approve = document.createElement("button");
|
||||||
approve.textContent = "Approve";
|
approve.textContent = "Approve";
|
||||||
approve.addEventListener("click", () => approveAction(action.id));
|
approve.addEventListener("click", () => approveAction(action.id));
|
||||||
card.append(title, endpoint, payload, approve);
|
const decline = document.createElement("button");
|
||||||
|
decline.className = "decline-button";
|
||||||
|
decline.textContent = "Decline";
|
||||||
|
decline.addEventListener("click", () => declineAction(action.id));
|
||||||
|
const controls = document.createElement("div");
|
||||||
|
controls.className = "pending-controls";
|
||||||
|
controls.append(decline, approve);
|
||||||
|
card.append(title, endpoint, payload, controls);
|
||||||
pendingEl.appendChild(card);
|
pendingEl.appendChild(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,6 +325,24 @@ async function approveAction(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function declineAction(id) {
|
||||||
|
statusEl.textContent = "Declining";
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/decline/${id}`, { method: "POST" });
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.error) {
|
||||||
|
addMessage("assistant warning-message", `Decline failed: ${result.error}`);
|
||||||
|
} else {
|
||||||
|
addMessage("assistant", `Declined pending action: ${result.pending_action?.label || id}`);
|
||||||
|
}
|
||||||
|
await refreshPending();
|
||||||
|
} catch (error) {
|
||||||
|
addMessage("assistant warning-message", `Decline failed: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
statusEl.textContent = "Ready";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshPending() {
|
async function refreshPending() {
|
||||||
const response = await fetch("/api/pending-actions");
|
const response = await fetch("/api/pending-actions");
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|||||||
@@ -447,11 +447,23 @@ pre {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pending-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.pending-card button {
|
.pending-card button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.decline-button {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
.shell {
|
.shell {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
Reference in New Issue
Block a user