.agents/skills/builtin-tool/references/ui/intervention.md
Lifecycle: rendered before the executor runs for APIs whose manifest sets humanIntervention. The user sees a preview of the args, can edit them, then approves or skips/cancels.
Add for destructive or sensitive ops: shell commands, file writes, file moves, payments, message broadcasts.
BuiltinInterventionProps<Args>)interface BuiltinInterventionProps<Arguments = any> {
apiName?: string;
args: Arguments;
identifier?: string;
interactionMode?: 'approval' | 'custom';
messageId: string;
/** Called when the user edits the args; the approve action awaits this. */
onArgsChange?: (args: Arguments) => void | Promise<void>;
/** Called on approve / skip / cancel. */
onInteractionAction?: (
action:
| { type: 'submit'; payload: Record<string, unknown> }
| { type: 'skip'; payload?: Record<string, unknown>; reason?: string }
| { type: 'cancel'; payload?: Record<string, unknown> },
) => Promise<void>;
/** Register a callback to flush pending saves before approval. Returns cleanup. */
registerBeforeApprove?: (id: string, callback: () => void | Promise<void>) => () => void;
}
packages/builtin-tool-local-system/src/client/Intervention/RunCommand/index.tsx:
import type { RunCommandParams } from '@lobechat/electron-client-ipc';
import type { BuiltinInterventionProps } from '@lobechat/types';
import { Flexbox, Highlighter, Text } from '@lobehub/ui';
import { memo } from 'react';
const RunCommand = memo<BuiltinInterventionProps<RunCommandParams>>(({ args }) => {
const { description, command, timeout } = args;
return (
<Flexbox gap={8}>
<Flexbox horizontal justify="space-between">
{description && <Text>{description}</Text>}
{timeout && (
<Text style={{ fontSize: 12 }} type="secondary">
timeout: {formatTimeout(timeout)}
</Text>
)}
</Flexbox>
{command && (
<Highlighter wrap language="sh" showLanguage={false} variant="outlined">
{command}
</Highlighter>
)}
</Flexbox>
);
});
export default RunCommand;
onArgsChange and is usually inline (click to edit a code block, etc.).registerBeforeApprove(id, flushFn) so the approve action waits for the debounce to flush. Always return the cleanup function.onInteractionAction({ type: 'submit', payload }) when the user approves; 'skip' if they skip with a reason; 'cancel' if they cancel the whole turn.interventionAudit.ts in the package root if the tool needs scope/path validation before approval (see local-system/src/interventionAudit.ts).client/Intervention/index.tsimport { LocalSystemApiName } from '../..';
import EditLocalFile from './EditLocalFile';
import RunCommand from './RunCommand';
import WriteFile from './WriteFile';
/* … */
export const LocalSystemInterventions = {
[LocalSystemApiName.editLocalFile]: EditLocalFile,
[LocalSystemApiName.runCommand]: RunCommand,
[LocalSystemApiName.writeLocalFile]: WriteFile,
/* one entry per API that needs approval */
};