mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(approvals): preserve lifecycle refs on reopen
This commit is contained in:
@@ -90,6 +90,7 @@ const SystemAgentApprovalAllowedDecisionsSchema = Type.Tuple([
|
||||
export const ExecApprovalPresentationSchema = Type.Object(
|
||||
{
|
||||
kind: Type.Literal("exec"),
|
||||
instanceId: Type.Optional(NonEmptyString),
|
||||
commandText: NonEmptyString,
|
||||
commandPreview: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
||||
warningText: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
||||
@@ -108,6 +109,7 @@ export const ExecApprovalPresentationSchema = Type.Object(
|
||||
/** Plugin-supplied reviewer text safe to persist and render across surfaces. */
|
||||
export const PluginApprovalPresentationSchema = closedObject({
|
||||
kind: Type.Literal("plugin"),
|
||||
instanceId: Type.Optional(NonEmptyString),
|
||||
title: Type.String({ minLength: 1, maxLength: 80 }),
|
||||
description: Type.String({ minLength: 1, maxLength: 512 }),
|
||||
detail: Type.Optional(Type.String({ minLength: 1, maxLength: 16_384 })),
|
||||
@@ -121,6 +123,7 @@ export const PluginApprovalPresentationSchema = closedObject({
|
||||
/** Reviewer-safe OpenClaw system change. Exact operation stays host-local. */
|
||||
export const SystemAgentApprovalPresentationSchema = closedObject({
|
||||
kind: Type.Literal("system-agent"),
|
||||
instanceId: Type.Optional(NonEmptyString),
|
||||
title: Type.String({ minLength: 1, maxLength: 80 }),
|
||||
description: Type.String({ minLength: 1, maxLength: 512 }),
|
||||
proposalHash: Type.String({ pattern: "^[a-f0-9]{64}$" }),
|
||||
|
||||
@@ -296,6 +296,7 @@ export class ExecApprovalManager<TPayload = ExecApprovalRequestPayload> {
|
||||
? buildApprovalPresentation({
|
||||
kind: this.approvalKind,
|
||||
request: record.request,
|
||||
instanceId: record.instanceId,
|
||||
allowedDecisions: allowedDecisions ?? [],
|
||||
})
|
||||
: null;
|
||||
@@ -398,6 +399,7 @@ export class ExecApprovalManager<TPayload = ExecApprovalRequestPayload> {
|
||||
const presentation = buildApprovalPresentation({
|
||||
kind: this.approvalKind,
|
||||
request: record.request,
|
||||
instanceId: record.instanceId,
|
||||
allowedDecisions: normalizeAllowedDecisions(
|
||||
this.options.resolveAllowedDecisions?.(record.request),
|
||||
),
|
||||
|
||||
@@ -444,7 +444,11 @@ function decodeOperatorApprovalRow(row: OperatorApprovalRow): OperatorApprovalRe
|
||||
if (
|
||||
presentation.kind !== kind ||
|
||||
row.resolution_ref !==
|
||||
buildApprovalResolutionRef({ approvalId: row.approval_id, approvalKind: kind }) ||
|
||||
buildApprovalResolutionRef({
|
||||
approvalId: row.approval_id,
|
||||
approvalKind: kind,
|
||||
instanceId: presentation.instanceId,
|
||||
}) ||
|
||||
!hasValidLifecycleTuple({ row, status, decision, terminalReason, resolverKind }) ||
|
||||
(status === "allowed" &&
|
||||
(!decision || !Array.prototype.includes.call(presentation.allowedDecisions, decision)))
|
||||
|
||||
@@ -45,6 +45,7 @@ function sanitizeOptionalSingleLine(value: unknown): string | null {
|
||||
|
||||
function buildExecApprovalPresentation(params: {
|
||||
request: unknown;
|
||||
instanceId?: string;
|
||||
allowedDecisions: readonly ApprovalDecision[];
|
||||
}): ApprovalPresentation | null {
|
||||
if (!isRecord(params.request)) {
|
||||
@@ -61,6 +62,7 @@ function buildExecApprovalPresentation(params: {
|
||||
: null;
|
||||
return {
|
||||
kind: "exec",
|
||||
...(params.instanceId ? { instanceId: params.instanceId } : {}),
|
||||
commandText,
|
||||
commandPreview,
|
||||
warningText,
|
||||
@@ -73,6 +75,7 @@ function buildExecApprovalPresentation(params: {
|
||||
|
||||
function buildPluginApprovalPresentation(params: {
|
||||
request: unknown;
|
||||
instanceId?: string;
|
||||
allowedDecisions: readonly ApprovalDecision[];
|
||||
}): ApprovalPresentation | null {
|
||||
if (!isRecord(params.request)) {
|
||||
@@ -104,6 +107,7 @@ function buildPluginApprovalPresentation(params: {
|
||||
: null;
|
||||
return {
|
||||
kind: "plugin",
|
||||
...(params.instanceId ? { instanceId: params.instanceId } : {}),
|
||||
title,
|
||||
description,
|
||||
...(detail ? { detail } : {}),
|
||||
@@ -117,6 +121,7 @@ function buildPluginApprovalPresentation(params: {
|
||||
|
||||
function buildSystemAgentApprovalPresentation(params: {
|
||||
request: unknown;
|
||||
instanceId?: string;
|
||||
allowedDecisions: readonly ApprovalDecision[];
|
||||
}): ApprovalPresentation | null {
|
||||
if (!isRecord(params.request)) {
|
||||
@@ -130,6 +135,7 @@ function buildSystemAgentApprovalPresentation(params: {
|
||||
}
|
||||
return {
|
||||
kind: "system-agent",
|
||||
...(params.instanceId ? { instanceId: params.instanceId } : {}),
|
||||
title: truncateUtf16Safe(sanitizeExecApprovalDisplayText(title), 80),
|
||||
description: truncateUtf16Safe(sanitizeExecApprovalWarningText(description), 512),
|
||||
proposalHash: request.proposalHash,
|
||||
@@ -142,6 +148,7 @@ function buildSystemAgentApprovalPresentation(params: {
|
||||
export function buildApprovalPresentation(params: {
|
||||
kind: ApprovalKind;
|
||||
request: unknown;
|
||||
instanceId?: string;
|
||||
allowedDecisions: readonly ApprovalDecision[];
|
||||
}): ApprovalPresentation | null {
|
||||
if (params.kind === "exec") {
|
||||
|
||||
@@ -17,11 +17,14 @@ export function ensureOperatorApprovalResolutionRefs(db: DatabaseSync): void {
|
||||
runSqliteImmediateTransactionSync(db, () => {
|
||||
ensureColumn(db, "operator_approvals", "resolution_ref TEXT");
|
||||
const rows = db
|
||||
.prepare("SELECT approval_id, kind, resolution_ref FROM operator_approvals")
|
||||
.prepare(
|
||||
"SELECT approval_id, kind, resolution_ref, presentation_json FROM operator_approvals",
|
||||
)
|
||||
.all() as Array<{
|
||||
approval_id?: unknown;
|
||||
kind?: unknown;
|
||||
resolution_ref?: unknown;
|
||||
presentation_json?: unknown;
|
||||
}>;
|
||||
const update = db.prepare(
|
||||
"UPDATE operator_approvals SET resolution_ref = ? WHERE approval_id = ?",
|
||||
@@ -33,9 +36,16 @@ export function ensureOperatorApprovalResolutionRefs(db: DatabaseSync): void {
|
||||
) {
|
||||
throw new Error("operator approval row cannot be assigned a transport reference");
|
||||
}
|
||||
const presentation =
|
||||
typeof row.presentation_json === "string"
|
||||
? safeParseJsonRecord(row.presentation_json)
|
||||
: undefined;
|
||||
const instanceId =
|
||||
typeof presentation?.instanceId === "string" ? presentation.instanceId : undefined;
|
||||
const resolutionRef = buildApprovalResolutionRef({
|
||||
approvalId: row.approval_id,
|
||||
approvalKind: row.kind,
|
||||
instanceId,
|
||||
});
|
||||
if (row.resolution_ref !== resolutionRef) {
|
||||
update.run(resolutionRef, row.approval_id);
|
||||
|
||||
Reference in New Issue
Block a user