Shub/let listener mode control (#9584)
* feat: add two-way mode control for listener connections Enable bidirectional permission mode control between letta-cloud UI and letta-code instances. **Backend:** - Added ModeChangeMessage and ModeChangedMessage to WebSocket protocol - Added sendModeChange endpoint (/v1/listeners/:connectionId/mode) - listenersRouter publishes mode_change via Redis Pub/Sub - listenersHandler handles mode_changed acknowledgments from letta-code - Stores current mode in Redis for UI state sync **Contract:** - Added sendModeChange contract with PermissionModeSchema - 4 modes: default, acceptEdits, plan, bypassPermissions **Frontend:** - Extended PermissionMode type to 4 modes (was 2: ask/never) - PermissionModeSelector now shows all 4 modes with descriptions - Added disabled prop (grayed out when Cloud orchestrator selected) - PermissionModeContext.sendModeChangeToDevice() calls API - AgentMessenger sends mode changes to device on mode/device change - Updated auto-approval logic (only in Cloud mode, only for bypassPermissions) - Updated inputMode logic (device handles approvals, not cloud) **Translations:** - Updated en.json with 4 mode labels and descriptions - Removed legacy "askAlways" and "neverAsk" keys **Mode Behavior:** - default: Ask permission for each tool - acceptEdits: Auto-approve file edits only - plan: Read-only exploration (denies writes) - bypassPermissions: Auto-approve everything **Lint Fixes:** - Removed unused imports and functions from trackingMiddleware.ts 🐾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * fix: store mode in connectionData and show approvals for all modes **Backend:** - Fixed Redis WRONGTYPE error - store currentMode inside connectionData object - Changed const connectionData to let connectionData (needs mutation) - Updated mode_changed handler to reassign entire connectionData object - Updated ping handler for consistency (also reassigns connectionData) - Added currentMode field to ListenerConnectionSchema (optional) **Frontend:** - Simplified inputMode logic - always show approval UI when toolCallsToApprove.length > 0 - Removed mode-specific approval filtering (show approvals even in bypass/acceptEdits for visibility) - Users can see what tools are being auto-approved during execution **Why:** - Redis key is a JSON string (via setRedisData), not a hash - Cannot use hset on string keys - causes WRONGTYPE error - Must update entire object via setRedisData like ping handler does - Approval visibility helpful for debugging/understanding agent behavior 🐾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * fix: use useMutation hook for sendModeChange instead of direct call cloudAPI is initialized via initTsrReactQuery, so sendModeChange is a mutation hook object, not a callable function. Use .useMutation() at the component level and mutateAsync in the callback. Co-authored-by: Shubham Naik <4shub@users.noreply.github.com> * chore: update logs --------- Co-authored-by: Letta <noreply@letta.com> Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: Shubham Naik <4shub@users.noreply.github.com>
This commit is contained in:
committed by
Caren Thomas
parent
73c9b14fa9
commit
34bab3cf9a
@@ -25983,6 +25983,15 @@
|
||||
},
|
||||
"lastHeartbeat": {
|
||||
"type": "number"
|
||||
},
|
||||
"currentMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"acceptEdits",
|
||||
"plan",
|
||||
"bypassPermissions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -26245,6 +26254,86 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/listeners/{connectionId}/mode": {
|
||||
"post": {
|
||||
"description": "Change the permission mode of a specific listener connection",
|
||||
"summary": "Change Listener Mode",
|
||||
"tags": ["listeners"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "connectionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"operationId": "listeners.sendModeChange",
|
||||
"requestBody": {
|
||||
"description": "Body",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"acceptEdits",
|
||||
"plan",
|
||||
"bypassPermissions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["mode"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "200",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["success", "message"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "404",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["errorCode", "message"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
||||
Reference in New Issue
Block a user