mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 12:46:58 +10:00
204 lines
39 KiB
TypeScript
204 lines
39 KiB
TypeScript
|
|
import { c as _c } from "react/compiler-runtime";
|
||
|
|
import { feature } from 'bun:bundle';
|
||
|
|
import { APIUserAbortError } from '@anthropic-ai/sdk';
|
||
|
|
import * as React from 'react';
|
||
|
|
import { useCallback } from 'react';
|
||
|
|
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js';
|
||
|
|
import { sanitizeToolNameForAnalytics } from 'src/services/analytics/metadata.js';
|
||
|
|
import type { ToolUseConfirm } from '../components/permissions/PermissionRequest.js';
|
||
|
|
import { Text } from '../ink.js';
|
||
|
|
import type { ToolPermissionContext, Tool as ToolType, ToolUseContext } from '../Tool.js';
|
||
|
|
import { consumeSpeculativeClassifierCheck, peekSpeculativeClassifierCheck } from '../tools/BashTool/bashPermissions.js';
|
||
|
|
import { BASH_TOOL_NAME } from '../tools/BashTool/toolName.js';
|
||
|
|
import type { AssistantMessage } from '../types/message.js';
|
||
|
|
import { recordAutoModeDenial } from '../utils/autoModeDenials.js';
|
||
|
|
import { clearClassifierChecking, setClassifierApproval, setYoloClassifierApproval } from '../utils/classifierApprovals.js';
|
||
|
|
import { logForDebugging } from '../utils/debug.js';
|
||
|
|
import { AbortError } from '../utils/errors.js';
|
||
|
|
import { logError } from '../utils/log.js';
|
||
|
|
import type { PermissionDecision } from '../utils/permissions/PermissionResult.js';
|
||
|
|
import { hasPermissionsToUseTool } from '../utils/permissions/permissions.js';
|
||
|
|
import { jsonStringify } from '../utils/slowOperations.js';
|
||
|
|
import { handleCoordinatorPermission } from './toolPermission/handlers/coordinatorHandler.js';
|
||
|
|
import { handleInteractivePermission } from './toolPermission/handlers/interactiveHandler.js';
|
||
|
|
import { handleSwarmWorkerPermission } from './toolPermission/handlers/swarmWorkerHandler.js';
|
||
|
|
import { createPermissionContext, createPermissionQueueOps } from './toolPermission/PermissionContext.js';
|
||
|
|
import { logPermissionDecision } from './toolPermission/permissionLogging.js';
|
||
|
|
export type CanUseToolFn<Input extends Record<string, unknown> = Record<string, unknown>> = (tool: ToolType, input: Input, toolUseContext: ToolUseContext, assistantMessage: AssistantMessage, toolUseID: string, forceDecision?: PermissionDecision<Input>) => Promise<PermissionDecision<Input>>;
|
||
|
|
function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
|
||
|
|
const $ = _c(3);
|
||
|
|
let t0;
|
||
|
|
if ($[0] !== setToolPermissionContext || $[1] !== setToolUseConfirmQueue) {
|
||
|
|
t0 = async (tool, input, toolUseContext, assistantMessage, toolUseID, forceDecision) => new Promise(resolve => {
|
||
|
|
const ctx = createPermissionContext(tool, input, toolUseContext, assistantMessage, toolUseID, setToolPermissionContext, createPermissionQueueOps(setToolUseConfirmQueue));
|
||
|
|
if (ctx.resolveIfAborted(resolve)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const decisionPromise = forceDecision !== undefined ? Promise.resolve(forceDecision) : hasPermissionsToUseTool(tool, input, toolUseContext, assistantMessage, toolUseID);
|
||
|
|
return decisionPromise.then(async result => {
|
||
|
|
if (result.behavior === "allow") {
|
||
|
|
if (ctx.resolveIfAborted(resolve)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (feature("TRANSCRIPT_CLASSIFIER") && result.decisionReason?.type === "classifier" && result.decisionReason.classifier === "auto-mode") {
|
||
|
|
setYoloClassifierApproval(toolUseID, result.decisionReason.reason);
|
||
|
|
}
|
||
|
|
ctx.logDecision({
|
||
|
|
decision: "accept",
|
||
|
|
source: "config"
|
||
|
|
});
|
||
|
|
resolve(ctx.buildAllow(result.updatedInput ?? input, {
|
||
|
|
decisionReason: result.decisionReason
|
||
|
|
}));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const appState = toolUseContext.getAppState();
|
||
|
|
const description = await tool.description(input as never, {
|
||
|
|
isNonInteractiveSession: toolUseContext.options.isNonInteractiveSession,
|
||
|
|
toolPermissionContext: appState.toolPermissionContext,
|
||
|
|
tools: toolUseContext.options.tools
|
||
|
|
});
|
||
|
|
if (ctx.resolveIfAborted(resolve)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
switch (result.behavior) {
|
||
|
|
case "deny":
|
||
|
|
{
|
||
|
|
logPermissionDecision({
|
||
|
|
tool,
|
||
|
|
input,
|
||
|
|
toolUseContext,
|
||
|
|
messageId: ctx.messageId,
|
||
|
|
toolUseID
|
||
|
|
}, {
|
||
|
|
decision: "reject",
|
||
|
|
source: "config"
|
||
|
|
});
|
||
|
|
if (feature("TRANSCRIPT_CLASSIFIER") && result.decisionReason?.type === "classifier" && result.decisionReason.classifier === "auto-mode") {
|
||
|
|
recordAutoModeDenial({
|
||
|
|
toolName: tool.name,
|
||
|
|
display: description,
|
||
|
|
reason: result.decisionReason.reason ?? "",
|
||
|
|
timestamp: Date.now()
|
||
|
|
});
|
||
|
|
toolUseContext.addNotification?.({
|
||
|
|
key: "auto-mode-denied",
|
||
|
|
priority: "immediate",
|
||
|
|
jsx: <><Text color="error">{tool.userFacingName(input).toLowerCase()} denied by auto mode</Text><Text dimColor={true}> · /permissions</Text></>
|
||
|
|
});
|
||
|
|
}
|
||
|
|
resolve(result);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case "ask":
|
||
|
|
{
|
||
|
|
if (appState.toolPermissionContext.awaitAutomatedChecksBeforeDialog) {
|
||
|
|
const coordinatorDecision = await handleCoordinatorPermission({
|
||
|
|
ctx,
|
||
|
|
...(feature("BASH_CLASSIFIER") ? {
|
||
|
|
pendingClassifierCheck: result.pendingClassifierCheck
|
||
|
|
} : {}),
|
||
|
|
updatedInput: result.updatedInput,
|
||
|
|
suggestions: result.suggestions,
|
||
|
|
permissionMode: appState.toolPermissionContext.mode
|
||
|
|
});
|
||
|
|
if (coordinatorDecision) {
|
||
|
|
resolve(coordinatorDecision);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (ctx.resolveIfAborted(resolve)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const swarmDecision = await handleSwarmWorkerPermission({
|
||
|
|
ctx,
|
||
|
|
description,
|
||
|
|
...(feature("BASH_CLASSIFIER") ? {
|
||
|
|
pendingClassifierCheck: result.pendingClassifierCheck
|
||
|
|
} : {}),
|
||
|
|
updatedInput: result.updatedInput,
|
||
|
|
suggestions: result.suggestions
|
||
|
|
});
|
||
|
|
if (swarmDecision) {
|
||
|
|
resolve(swarmDecision);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (feature("BASH_CLASSIFIER") && result.pendingClassifierCheck && tool.name === BASH_TOOL_NAME && !appState.toolPermissionContext.awaitAutomatedChecksBeforeDialog) {
|
||
|
|
const speculativePromise = peekSpeculativeClassifierCheck((input as {
|
||
|
|
command: string;
|
||
|
|
}).command);
|
||
|
|
if (speculativePromise) {
|
||
|
|
const raceResult = await Promise.race([speculativePromise.then(_temp), new Promise(_temp2)]);
|
||
|
|
if (ctx.resolveIfAborted(resolve)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (raceResult.type === "result" && raceResult.result.matches && raceResult.result.confidence === "high" && feature("BASH_CLASSIFIER")) {
|
||
|
|
consumeSpeculativeClassifierCheck((input as {
|
||
|
|
command: string;
|
||
|
|
}).command);
|
||
|
|
const matchedRule = raceResult.result.matchedDescription ?? undefined;
|
||
|
|
if (matchedRule) {
|
||
|
|
setClassifierApproval(toolUseID, matchedRule);
|
||
|
|
}
|
||
|
|
ctx.logDecision({
|
||
|
|
decision: "accept",
|
||
|
|
source: {
|
||
|
|
type: "classifier"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
resolve(ctx.buildAllow(result.updatedInput ?? input as Record<string, unknown>, {
|
||
|
|
decisionReason: {
|
||
|
|
type: "classifier" as const,
|
||
|
|
classifier: "bash_allow" as const,
|
||
|
|
reason: `Allowed by prompt rule: "${raceResult.result.matchedDescription}"`
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
handleInteractivePermission({
|
||
|
|
ctx,
|
||
|
|
description,
|
||
|
|
result,
|
||
|
|
awaitAutomatedChecksBeforeDialog: appState.toolPermissionContext.awaitAutomatedChecksBeforeDialog,
|
||
|
|
bridgeCallbacks: feature("BRIDGE_MODE") ? appState.replBridgePermissionCallbacks : undefined,
|
||
|
|
channelCallbacks: feature("KAIROS") || feature("KAIROS_CHANNELS") ? appState.channelPermissionCallbacks : undefined
|
||
|
|
}, resolve);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).catch(error => {
|
||
|
|
if (error instanceof AbortError || error instanceof APIUserAbortError) {
|
||
|
|
logForDebugging(`Permission check threw ${error.constructor.name} for tool=${tool.name}: ${error.message}`);
|
||
|
|
ctx.logCancelled();
|
||
|
|
resolve(ctx.cancelAndAbort(undefined, true));
|
||
|
|
} else {
|
||
|
|
logError(error);
|
||
|
|
resolve(ctx.cancelAndAbort(undefined, true));
|
||
|
|
}
|
||
|
|
}).finally(() => {
|
||
|
|
clearClassifierChecking(toolUseID);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
$[0] = setToolPermissionContext;
|
||
|
|
$[1] = setToolUseConfirmQueue;
|
||
|
|
$[2] = t0;
|
||
|
|
} else {
|
||
|
|
t0 = $[2];
|
||
|
|
}
|
||
|
|
return t0;
|
||
|
|
}
|
||
|
|
function _temp2(res) {
|
||
|
|
return setTimeout(res, 2000, {
|
||
|
|
type: "timeout" as const
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function _temp(r) {
|
||
|
|
return {
|
||
|
|
type: "result" as const,
|
||
|
|
result: r
|
||
|
|
};
|
||
|
|
}
|
||
|
|
export default useCanUseTool;
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmZWF0dXJlIiwiQVBJVXNlckFib3J0RXJyb3IiLCJSZWFjdCIsInVzZUNhbGxiYWNrIiwiQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyIsImxvZ0V2ZW50Iiwic2FuaXRpemVUb29sTmFtZUZvckFuYWx5dGljcyIsIlRvb2xVc2VDb25maXJtIiwiVGV4dCIsIlRvb2xQZXJtaXNzaW9uQ29udGV4dCIsIlRvb2wiLCJUb29sVHlwZSIsIlRvb2xVc2VDb250ZXh0IiwiY29uc3VtZVNwZWN1bGF0aXZlQ2xhc3NpZmllckNoZWNrIiwicGVla1NwZWN1bGF0aXZlQ2xhc3NpZmllckNoZWNrIiwiQkFTSF9UT09MX05BTUUiLCJBc3Npc3RhbnRNZXNzYWdlIiwicmVjb3JkQXV0b01vZGVEZW5pYWwiLCJjbGVhckNsYXNzaWZpZXJDaGVja2luZyIsInNldENsYXNzaWZpZXJBcHByb3ZhbCIsInNldFlvbG9DbGFzc2lmaWVyQXBwcm92YWwiLCJsb2dGb3JEZWJ1Z2dpbmciLCJBYm9ydEVycm9yIiwibG9nRXJyb3IiLCJQZXJtaXNzaW9uRGVjaXNpb24iLCJoYXNQZXJtaXNzaW9uc1RvVXNlVG9vbCIsImpzb25TdHJpbmdpZnkiLCJoYW5kbGVDb29yZGluYXRvclBlcm1pc3Npb24iLCJoYW5kbGVJbnRlcmFjdGl2ZVBlcm1pc3Npb24iLCJoYW5kbGVTd2FybVdvcmtlclBlcm1pc3Npb24iLCJjcmVhdGVQZXJtaXNzaW9uQ29udGV4dCIsImNyZWF0ZVBlcm1pc3Npb25RdWV1ZU9wcyIsImxvZ1Blcm1pc3Npb25EZWNpc2lvbiIsIkNhblVzZVRvb2xGbiIsIlJlY29yZCIsInRvb2wiLCJpbnB1dCIsIklucHV0IiwidG9vbFVzZUNvbnRleHQiLCJhc3Npc3RhbnRNZXNzYWdlIiwidG9vbFVzZUlEIiwiZm9yY2VEZWNpc2lvbiIsIlByb21pc2UiLCJ1c2VDYW5Vc2VUb29sIiwic2V0VG9vbFVzZUNvbmZpcm1RdWV1ZSIsInNldFRvb2xQZXJtaXNzaW9uQ29udGV4dCIsIiQiLCJfYyIsInQwIiwicmVzb2x2ZSIsImN0eCIsInJlc29sdmVJZkFib3J0ZWQiLCJkZWNpc2lvblByb21pc2UiLCJ1bmRlZmluZWQiLCJ0aGVuIiwicmVzdWx0IiwiYmVoYXZpb3IiLCJkZWNpc2lvblJlYXNvbiIsInR5cGUiLCJjbGFzc2lmaWVyIiwicmVhc29uIiwibG9nRGVjaXNpb24iLCJkZWNpc2lvbiIsInNvdXJjZSIsImJ1aWxkQWxsb3ciLCJ1cGRhdGVkSW5wdXQiLCJhcHBTdGF0ZSIsImdldEFwcFN0YXRlIiwiZGVzY3JpcHRpb24iLCJpc05vbkludGVyYWN0aXZlU2Vzc2lvbiIsIm9wdGlvbnMiLCJ0b29sUGVybWlzc2lvbkNvbnRleHQiLCJ0b29scyIsIm1lc3NhZ2VJZCIsInRvb2xOYW1lIiwibmFtZSIsImRpc3BsYXkiLCJ0aW1lc3RhbXAiLCJEYXRlIiwibm93IiwiYWRkTm90aWZpY2F0aW9uIiwia2V5IiwicHJpb3JpdHkiLCJqc3giLCJ1c2VyRmFjaW5nTmFtZSIsInRvTG93ZXJDYXNlIiwiYXdhaXRBdXRvbWF0ZWRDaGVja3NCZWZvcmVEaWFsb2ciLCJjb29yZGluYXRvckRlY2lzaW9uIiwicGVuZGluZ0NsYXNzaWZpZXJDaGVjayIsInN1Z2dlc3Rpb25zIiwicGVybWlzc2lvbk1vZGUiLCJtb2RlIiwic3dhcm1EZWNpc2lvbiIsInNwZWN1bGF0aXZlUHJvbWlzZSIsImNvbW1hbmQiLCJyYWNlUmVzdWx0IiwicmFjZSIsIl90ZW1wIiwiX3RlbXAyIiwibWF0Y2hlcyIsImNvbmZpZGVuY2UiLCJtYXRjaGVkUnVsZSIsIm1hdGNoZWREZXNjcmlwdGlvbiIsImNvbnN0IiwiYnJpZGdlQ2FsbGJhY2tzIiwicmVwbEJyaWRnZVBlcm1pc3Npb25DYWxsYmFja3MiLCJjaGFubmVsQ2FsbGJhY2tzIiwiY2hhbm5lbFBlcm1pc3Npb25DYWxsYmFja3MiLCJjYXRjaCIsImVycm9yIiwiY29uc3RydWN0b3IiLCJtZXNzYWdlIiwibG9nQ2FuY2VsbGVkIiwiY2FuY2VsQW5kQWJvcnQiLCJmaW5hbGx5IiwicmVzIiwic2V0VGltZW91dCIsInIiXSwic291cmNlcyI6WyJ1c2VDYW5Vc2VUb29sLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBmZWF0dXJlIH0gZnJvbSAnYnVuOmJ1bmRsZSdcbmltcG9ydCB7IEFQSVVzZXJBYm9ydEVycm9yIH0gZnJvbSAnQGFudGhyb3BpYy1haS9zZGsnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUNhbGxiYWNrIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQge1xuICB0eXBlIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gIGxvZ0V2ZW50LFxufSBmcm9tICdzcmMvc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHsgc2FuaXRpemVUb29sTmFtZUZvckFuYWx5dGljcyB9IGZyb20gJ3NyYy9zZXJ2aWNlcy9hbmFseXRpY3MvbWV0YWRhdGEuanMnXG5pbXBvcnQgdHlwZSB7IFRvb2xVc2VDb25maXJtIH0gZnJvbSAnLi4vY29tcG9uZW50cy9wZXJtaXNzaW9ucy9QZXJtaXNzaW9uUmVxdWVzdC5qcydcbmltcG9ydCB7IFRleHQgfSBmcm9tICcuLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7XG4gIFRvb2xQZXJtaXNzaW9uQ29udGV4dCxcbiAgVG9vbCBhcyBUb29sVHlwZSxcbiAgVG9vbFVzZUNvbnRleHQsXG59IGZyb20gJy4uL1Rvb2wuanMnXG5pbXBvcnQge1xuICBjb25zdW1lU3BlY3VsYXRpdmVDbGFzc2lmaWVyQ2hlY2ssXG4gIHBlZWtTcGVjdWxhdGl2ZUNsYXNzaWZpZXJDaGVjayxcbn0gZnJvbSAnLi4vdG9vbHMvQmFzaFRvb2wvYmFzaFBlcm1pc3Npb25zLmpzJ1xuaW1wb3J0IHsgQkFTSF9UT09MX05BTUUgfSBmcm9tICcuLi90b29scy9CYXNoVG9vbC90b29sTmFtZS5qcydcbmltcG9ydCB0eXBlIHsgQXNzaXN0YW50TWVzc2FnZSB9IGZyb20gJy4uL3R5cGVzL21lc3NhZ2UuanMnXG5pbXBvcnQgeyByZWNvcmRBdXRvTW9kZURlbmlhbCB9IGZyb20gJy4uL3V0aWxzL2F1dG9Nb2RlRGVuaWFscy5qcydcbmltcG9ydCB7XG4gIGNsZWFyQ2xhc3NpZmllckNoZWNraW5nLFxuICBzZXRDbGFzc2lmaWVyQXBwcm92YWwsXG4gIHNldFlvbG9DbGFzc2lmaWVyQXBwcm92YWwsXG59IGZyb20gJy4uL3V0aWxzL2NsYXNzaWZpZXJBcHByb3ZhbHMuanMnXG5pbXBvcnQgeyBsb2dGb3JEZWJ1Z2dpbmcgfSBmcm9tICcuLi91dGlscy9kZWJ1Zy5qcydcbml
|