feat(chat): full approval.request support (#1057)
This commit is contained in:
9 files changed
+630
-49
No files matched your search
@@ -283,7 +283,40 @@ object EventParser {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val patternKeys = (payload?.get("pattern_keys") as? List<*>)?.filterIsInstance<String>()
|
||||
WsEvent.ApprovalRequest(command, description, patternKeys, sessionId)
|
||||
val requestId = payload?.get("request_id") as? String
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val rawChoices = (payload?.get("choices") as? List<*>)?.filterIsInstance<String>()
|
||||
val allowPermanent = payload?.get("allow_permanent") as? Boolean
|
||||
val allowSession = payload?.get("allow_session") as? Boolean
|
||||
val smartDenied = payload?.get("smart_denied") as? Boolean
|
||||
// Backend default (server.py `_approval_request_payload`):
|
||||
// smart-denied → once/deny only; else once + session? + always? + deny.
|
||||
val choices =
|
||||
rawChoices ?: run {
|
||||
if (smartDenied == true) {
|
||||
listOf("once", "deny")
|
||||
} else {
|
||||
buildList {
|
||||
add("once")
|
||||
if (allowSession != false) {
|
||||
add("session")
|
||||
if (allowPermanent != false) add("always")
|
||||
}
|
||||
add("deny")
|
||||
}
|
||||
}
|
||||
}
|
||||
WsEvent.ApprovalRequest(
|
||||
command,
|
||||
description,
|
||||
patternKeys,
|
||||
sessionId,
|
||||
requestId,
|
||||
choices,
|
||||
allowPermanent,
|
||||
smartDenied,
|
||||
)
|
||||
}
|
||||
|
||||
"sudo.request" -> {
|
||||
|
||||
@@ -230,6 +230,30 @@ sealed class WsEvent {
|
||||
val description: String?,
|
||||
val patternKeys: List<String>?,
|
||||
val sessionId: String?,
|
||||
/**
|
||||
* Backend `request_id` — unique across sessions. Sent back as
|
||||
* `request_id` in `approval.respond` so the gateway resolves the
|
||||
* EXACT pending instead of FIFO-oldest (desktop parity:
|
||||
* `prompts.ts` `receiveApprovalRequest` + `approval.tsx` respond).
|
||||
* Null on legacy payloads → omit, FIFO-compatible.
|
||||
*/
|
||||
val requestId: String? = null,
|
||||
/**
|
||||
* Backend-advertised choices (e.g. `["once","session","always","deny"]`,
|
||||
* smart-denied → `["once","deny"]`). Null = legacy → UI falls back
|
||||
* to Run/Deny.
|
||||
*/
|
||||
val choices: List<String>? = null,
|
||||
/**
|
||||
* False when the backend won't honor a permanent allow (tirith
|
||||
* warning) → hide "Always allow" (desktop `allowPermanent` parity).
|
||||
*/
|
||||
val allowPermanent: Boolean? = null,
|
||||
/**
|
||||
* True when this is an owner override of a Smart DENY — backend
|
||||
* restricts to once/deny only (desktop `smartDenied` parity).
|
||||
*/
|
||||
val smartDenied: Boolean? = null,
|
||||
) : WsEvent()
|
||||
|
||||
// ── Sudo / secret requests ─────────────────────────────────────────
|
||||
|
||||
@@ -34,6 +34,12 @@ object WsMethods {
|
||||
const val PROMPT_BTW = "prompt.btw"
|
||||
const val CLARIFY_RESPOND = "clarify.respond"
|
||||
const val APPROVAL_RESPOND = "approval.respond"
|
||||
|
||||
/** Ack that the client rendered a pending approval (desktop parity). */
|
||||
const val APPROVAL_RECEIVED = "approval.received"
|
||||
|
||||
/** List unresolved approvals for a session (reconnect replay). */
|
||||
const val APPROVAL_PENDING = "approval.pending"
|
||||
const val SUDO_RESPOND = "sudo.respond"
|
||||
const val SECRET_RESPOND = "secret.respond"
|
||||
|
||||
|
||||
Reference in new issue
Block a user