66import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce" ;
77import type { ExecElevatedDefaults } from "./bash-tools.exec-types.js" ;
88
9+ // Short-lived in-memory handoff state for exec approval follow-up turns. The
10+ // handoff carries elevated defaults across the approval response without
11+ // persisting operator approval state longer than the TTL.
912const EXEC_APPROVAL_FOLLOWUP_IDEMPOTENCY_PREFIX = "exec-approval-followup:" ;
1013const EXEC_APPROVAL_FOLLOWUP_IDEMPOTENCY_NONCE_MARKER = ":nonce:" ;
1114const EXEC_APPROVAL_FOLLOWUP_RUNTIME_HANDOFF_TTL_MS = 5 * 60 * 1000 ;
@@ -66,6 +69,7 @@ function pruneExpiredExecApprovalFollowupRuntimeHandoffs(nowMs: number): void {
6669 }
6770}
6871
72+ /** Build the idempotency key used for an exec approval follow-up. */
6973export function buildExecApprovalFollowupIdempotencyKey ( params : {
7074 approvalId : string ;
7175 nonce ?: string ;
@@ -75,6 +79,7 @@ export function buildExecApprovalFollowupIdempotencyKey(params: {
7579 return nonce ? `${ base } ${ EXEC_APPROVAL_FOLLOWUP_IDEMPOTENCY_NONCE_MARKER } ${ nonce } ` : base ;
7680}
7781
82+ /** Parse the approval id embedded in a follow-up idempotency key. */
7883export function parseExecApprovalFollowupApprovalId ( idempotencyKey : string ) : string | undefined {
7984 const normalized = normalizeOptionalString ( idempotencyKey ) ;
8085 if ( ! normalized ?. startsWith ( EXEC_APPROVAL_FOLLOWUP_IDEMPOTENCY_PREFIX ) ) {
@@ -85,6 +90,7 @@ export function parseExecApprovalFollowupApprovalId(idempotencyKey: string): str
8590 return normalizeOptionalString ( nonceMarker >= 0 ? body . slice ( 0 , nonceMarker ) : body ) ;
8691}
8792
93+ /** Register a short-lived exec approval handoff for the next follow-up turn. */
8894export function registerExecApprovalFollowupRuntimeHandoff ( params : {
8995 approvalId : string ;
9096 sessionKey : string ;
@@ -121,6 +127,7 @@ export function registerExecApprovalFollowupRuntimeHandoff(params: {
121127 return { handoffId, idempotencyKey } ;
122128}
123129
130+ /** Consume a matching handoff once, validating approval/session/idempotency data. */
124131export function consumeExecApprovalFollowupRuntimeHandoff ( params : {
125132 handoffId ?: string ;
126133 approvalId ?: string ;
@@ -150,12 +157,15 @@ export function consumeExecApprovalFollowupRuntimeHandoff(params: {
150157 entry . idempotencyKey !== idempotencyKey ||
151158 entry . sessionKey !== sessionKey
152159 ) {
160+ // Handoffs are single-session capabilities; mismatched follow-up metadata
161+ // must not consume or expose the stored elevated defaults.
153162 return undefined ;
154163 }
155164 execApprovalFollowupRuntimeHandoffs . delete ( handoffId ) ;
156165 return cloneExecApprovalFollowupRuntimeHandoff ( entry ) ;
157166}
158167
168+ /** Clear exec approval follow-up handoffs between tests. */
159169export function resetExecApprovalFollowupRuntimeHandoffsForTests ( ) : void {
160170 execApprovalFollowupRuntimeHandoffs . clear ( ) ;
161171}
0 commit comments