fix: disable tool approve for old claude code version

This commit is contained in:
d-kimsuon
2025-10-14 12:18:29 +09:00
parent b483e7e918
commit a88ad89972
4 changed files with 53 additions and 60 deletions

View File

@@ -167,7 +167,7 @@ export const routes = (app: HonoAppType) => {
"query", "query",
z.object({ z.object({
basePath: z.string().optional().default("/"), basePath: z.string().optional().default("/"),
}) }),
), ),
async (c) => { async (c) => {
const { projectId } = c.req.param(); const { projectId } = c.req.param();
@@ -182,14 +182,14 @@ export const routes = (app: HonoAppType) => {
try { try {
const result = await getFileCompletion( const result = await getFileCompletion(
project.meta.projectPath, project.meta.projectPath,
basePath basePath,
); );
return c.json(result); return c.json(result);
} catch (error) { } catch (error) {
console.error("File completion error:", error); console.error("File completion error:", error);
return c.json({ error: "Failed to get file completion" }, 500); return c.json({ error: "Failed to get file completion" }, 500);
} }
} },
) )
.get("/projects/:projectId/claude-commands", async (c) => { .get("/projects/:projectId/claude-commands", async (c) => {
@@ -202,18 +202,18 @@ export const routes = (app: HonoAppType) => {
}).then((dirents) => }).then((dirents) =>
dirents dirents
.filter((d) => d.isFile() && d.name.endsWith(".md")) .filter((d) => d.isFile() && d.name.endsWith(".md"))
.map((d) => d.name.replace(/\.md$/, "")) .map((d) => d.name.replace(/\.md$/, "")),
), ),
project.meta.projectPath !== null project.meta.projectPath !== null
? readdir( ? readdir(
resolve(project.meta.projectPath, ".claude", "commands"), resolve(project.meta.projectPath, ".claude", "commands"),
{ {
withFileTypes: true, withFileTypes: true,
} },
).then((dirents) => ).then((dirents) =>
dirents dirents
.filter((d) => d.isFile() && d.name.endsWith(".md")) .filter((d) => d.isFile() && d.name.endsWith(".md"))
.map((d) => d.name.replace(/\.md$/, "")) .map((d) => d.name.replace(/\.md$/, "")),
) )
: [], : [],
]); ]);
@@ -274,7 +274,7 @@ export const routes = (app: HonoAppType) => {
z.object({ z.object({
fromRef: z.string().min(1, "fromRef is required"), fromRef: z.string().min(1, "fromRef is required"),
toRef: z.string().min(1, "toRef is required"), toRef: z.string().min(1, "toRef is required"),
}) }),
), ),
async (c) => { async (c) => {
const { projectId } = c.req.param(); const { projectId } = c.req.param();
@@ -289,7 +289,7 @@ export const routes = (app: HonoAppType) => {
const result = await getDiff( const result = await getDiff(
project.meta.projectPath, project.meta.projectPath,
fromRef, fromRef,
toRef toRef,
); );
return c.json(result); return c.json(result);
} catch (error) { } catch (error) {
@@ -299,7 +299,7 @@ export const routes = (app: HonoAppType) => {
} }
return c.json({ error: "Failed to get diff" }, 500); return c.json({ error: "Failed to get diff" }, 500);
} }
} },
) )
.get("/mcp/list", async (c) => { .get("/mcp/list", async (c) => {
@@ -313,7 +313,7 @@ export const routes = (app: HonoAppType) => {
"json", "json",
z.object({ z.object({
message: z.string(), message: z.string(),
}) }),
), ),
async (c) => { async (c) => {
const { projectId } = c.req.param(); const { projectId } = c.req.param();
@@ -325,13 +325,13 @@ export const routes = (app: HonoAppType) => {
} }
const task = await getTaskController( const task = await getTaskController(
c.get("config") c.get("config"),
).startOrContinueTask( ).startOrContinueTask(
{ {
projectId, projectId,
cwd: project.meta.projectPath, cwd: project.meta.projectPath,
}, },
message message,
); );
return c.json({ return c.json({
@@ -339,7 +339,7 @@ export const routes = (app: HonoAppType) => {
sessionId: task.sessionId, sessionId: task.sessionId,
userMessageId: task.userMessageId, userMessageId: task.userMessageId,
}); });
} },
) )
.post( .post(
@@ -348,7 +348,7 @@ export const routes = (app: HonoAppType) => {
"json", "json",
z.object({ z.object({
resumeMessage: z.string(), resumeMessage: z.string(),
}) }),
), ),
async (c) => { async (c) => {
const { projectId, sessionId } = c.req.param(); const { projectId, sessionId } = c.req.param();
@@ -360,14 +360,14 @@ export const routes = (app: HonoAppType) => {
} }
const task = await getTaskController( const task = await getTaskController(
c.get("config") c.get("config"),
).startOrContinueTask( ).startOrContinueTask(
{ {
projectId, projectId,
sessionId, sessionId,
cwd: project.meta.projectPath, cwd: project.meta.projectPath,
}, },
resumeMessage resumeMessage,
); );
return c.json({ return c.json({
@@ -375,7 +375,7 @@ export const routes = (app: HonoAppType) => {
sessionId: task.sessionId, sessionId: task.sessionId,
userMessageId: task.userMessageId, userMessageId: task.userMessageId,
}); });
} },
) )
.get("/tasks/alive", async (c) => { .get("/tasks/alive", async (c) => {
@@ -386,7 +386,7 @@ export const routes = (app: HonoAppType) => {
status: task.status, status: task.status,
sessionId: task.sessionId, sessionId: task.sessionId,
userMessageId: task.userMessageId, userMessageId: task.userMessageId,
}) }),
), ),
}); });
}) })
@@ -398,7 +398,7 @@ export const routes = (app: HonoAppType) => {
const { sessionId } = c.req.valid("json"); const { sessionId } = c.req.valid("json");
getTaskController(c.get("config")).abortTask(sessionId); getTaskController(c.get("config")).abortTask(sessionId);
return c.json({ message: "Task aborted" }); return c.json({ message: "Task aborted" });
} },
) )
.post( .post(
@@ -408,15 +408,15 @@ export const routes = (app: HonoAppType) => {
z.object({ z.object({
permissionRequestId: z.string(), permissionRequestId: z.string(),
decision: z.enum(["allow", "deny"]), decision: z.enum(["allow", "deny"]),
}) }),
), ),
async (c) => { async (c) => {
const permissionResponse = c.req.valid("json"); const permissionResponse = c.req.valid("json");
getTaskController(c.get("config")).respondToPermissionRequest( getTaskController(c.get("config")).respondToPermissionRequest(
permissionResponse permissionResponse,
); );
return c.json({ message: "Permission response received" }); return c.json({ message: "Permission response received" });
} },
) )
.get("/sse", async (c) => { .get("/sse", async (c) => {
@@ -426,7 +426,7 @@ export const routes = (app: HonoAppType) => {
const stream = writeTypeSafeSSE(rawStream); const stream = writeTypeSafeSSE(rawStream);
const onSessionListChanged = ( const onSessionListChanged = (
event: InternalEventDeclaration["sessionListChanged"] event: InternalEventDeclaration["sessionListChanged"],
) => { ) => {
stream.writeSSE("sessionListChanged", { stream.writeSSE("sessionListChanged", {
projectId: event.projectId, projectId: event.projectId,
@@ -434,7 +434,7 @@ export const routes = (app: HonoAppType) => {
}; };
const onSessionChanged = ( const onSessionChanged = (
event: InternalEventDeclaration["sessionChanged"] event: InternalEventDeclaration["sessionChanged"],
) => { ) => {
stream.writeSSE("sessionChanged", { stream.writeSSE("sessionChanged", {
projectId: event.projectId, projectId: event.projectId,
@@ -443,7 +443,7 @@ export const routes = (app: HonoAppType) => {
}; };
const onTaskChanged = ( const onTaskChanged = (
event: InternalEventDeclaration["taskChanged"] event: InternalEventDeclaration["taskChanged"],
) => { ) => {
stream.writeSSE("taskChanged", { stream.writeSSE("taskChanged", {
aliveTasks: event.aliveTasks, aliveTasks: event.aliveTasks,
@@ -467,7 +467,7 @@ export const routes = (app: HonoAppType) => {
}, },
async (err) => { async (err) => {
console.error("Streaming error:", err); console.error("Streaming error:", err);
} },
); );
}) })
); );

View File

@@ -19,7 +19,7 @@ export class ClaudeCodeExecutor {
? resolve(executablePath) ? resolve(executablePath)
: execSync("which claude", {}).toString().trim(); : execSync("which claude", {}).toString().trim();
this.claudeCodeVersion = ClaudeCodeVersion.fromCLIString( this.claudeCodeVersion = ClaudeCodeVersion.fromCLIString(
execSync(`${this.pathToClaudeCodeExecutable} --version`, {}).toString() execSync(`${this.pathToClaudeCodeExecutable} --version`, {}).toString(),
); );
} }
@@ -27,11 +27,11 @@ export class ClaudeCodeExecutor {
return { return {
enableToolApproval: enableToolApproval:
this.claudeCodeVersion?.greaterThanOrEqual( this.claudeCodeVersion?.greaterThanOrEqual(
new ClaudeCodeVersion({ major: 1, minor: 0, patch: 82 }) new ClaudeCodeVersion({ major: 1, minor: 0, patch: 82 }),
) ?? false, ) ?? false,
extractUuidFromSDKMessage: extractUuidFromSDKMessage:
this.claudeCodeVersion?.greaterThanOrEqual( this.claudeCodeVersion?.greaterThanOrEqual(
new ClaudeCodeVersion({ major: 1, minor: 0, patch: 86 }) new ClaudeCodeVersion({ major: 1, minor: 0, patch: 86 }),
) ?? false, ) ?? false,
}; };
} }

View File

@@ -46,7 +46,7 @@ export class ClaudeCodeTaskController {
return async ( return async (
toolName: string, toolName: string,
toolInput: Record<string, unknown>, toolInput: Record<string, unknown>,
_options: { signal: AbortSignal } _options: { signal: AbortSignal },
) => { ) => {
// If not in default mode, use the configured permission mode behavior // If not in default mode, use the configured permission mode behavior
if (this.config.permissionMode !== "default") { if (this.config.permissionMode !== "default") {
@@ -81,7 +81,7 @@ export class ClaudeCodeTaskController {
// Store the request // Store the request
this.pendingPermissionRequests.set( this.pendingPermissionRequests.set(
permissionRequest.id, permissionRequest.id,
permissionRequest permissionRequest,
); );
// Emit event to notify UI // Emit event to notify UI
@@ -92,7 +92,7 @@ export class ClaudeCodeTaskController {
// Wait for user response with timeout // Wait for user response with timeout
const response = await this.waitForPermissionResponse( const response = await this.waitForPermissionResponse(
permissionRequest.id, permissionRequest.id,
60000 60000,
); // 60 second timeout ); // 60 second timeout
if (response) { if (response) {
@@ -120,7 +120,7 @@ export class ClaudeCodeTaskController {
private async waitForPermissionResponse( private async waitForPermissionResponse(
permissionRequestId: string, permissionRequestId: string,
timeoutMs: number timeoutMs: number,
): Promise<PermissionResponse | null> { ): Promise<PermissionResponse | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const checkResponse = () => { const checkResponse = () => {
@@ -153,7 +153,7 @@ export class ClaudeCodeTaskController {
public get aliveTasks() { public get aliveTasks() {
return this.tasks.filter( return this.tasks.filter(
(task) => task.status === "running" || task.status === "paused" (task) => task.status === "running" || task.status === "paused",
); );
} }
@@ -163,10 +163,10 @@ export class ClaudeCodeTaskController {
projectId: string; projectId: string;
sessionId?: string; sessionId?: string;
}, },
message: string message: string,
): Promise<AliveClaudeCodeTask> { ): Promise<AliveClaudeCodeTask> {
const existingTask = this.aliveTasks.find( const existingTask = this.aliveTasks.find(
(task) => task.sessionId === currentSession.sessionId (task) => task.sessionId === currentSession.sessionId,
); );
if (existingTask) { if (existingTask) {
@@ -190,7 +190,7 @@ export class ClaudeCodeTaskController {
projectId: string; projectId: string;
sessionId?: string; sessionId?: string;
}, },
message: string message: string,
) { ) {
const { const {
generateMessages, generateMessages,
@@ -221,7 +221,7 @@ export class ClaudeCodeTaskController {
(resolve, reject) => { (resolve, reject) => {
aliveTaskResolve = resolve; aliveTaskResolve = resolve;
aliveTaskReject = reject; aliveTaskReject = reject;
} },
); );
let resolved = false; let resolved = false;
@@ -240,10 +240,10 @@ export class ClaudeCodeTaskController {
permissionMode: this.config.permissionMode, permissionMode: this.config.permissionMode,
canUseTool: this.createCanUseToolCallback( canUseTool: this.createCanUseToolCallback(
task.id, task.id,
task.baseSessionId task.baseSessionId,
), ),
abortController: abortController, abortController: abortController,
} },
)) { )) {
currentTask ??= this.aliveTasks.find((t) => t.id === task.id); currentTask ??= this.aliveTasks.find((t) => t.id === task.id);
@@ -257,13 +257,9 @@ export class ClaudeCodeTaskController {
// 初回の system message だとまだ history ファイルが作成されていないので // 初回の system message だとまだ history ファイルが作成されていないので
if (message.type === "user" || message.type === "assistant") { if (message.type === "user" || message.type === "assistant") {
// 本来は message.uuid の存在チェックをしたいが、古いバージョンでは存在しないことがある // 本来は message.uuid の存在チェックをしたいが、古いバージョンでは存在しないことがある
console.log(
"[DEBUG startTask] 9. Processing user/assistant message"
);
if (!resolved) { if (!resolved) {
console.log( console.log(
"[DEBUG startTask] 10. Resolving task for first time" "[DEBUG startTask] 10. Resolving task for first time",
); );
const runningTask: RunningClaudeCodeTask = { const runningTask: RunningClaudeCodeTask = {
@@ -283,12 +279,12 @@ export class ClaudeCodeTaskController {
}; };
this.tasks.push(runningTask); this.tasks.push(runningTask);
console.log( console.log(
"[DEBUG startTask] 11. About to call aliveTaskResolve" "[DEBUG startTask] 11. About to call aliveTaskResolve",
); );
aliveTaskResolve(runningTask); aliveTaskResolve(runningTask);
resolved = true; resolved = true;
console.log( console.log(
"[DEBUG startTask] 12. aliveTaskResolve called, resolved=true" "[DEBUG startTask] 12. aliveTaskResolve called, resolved=true",
); );
} }
@@ -298,13 +294,10 @@ export class ClaudeCodeTaskController {
await Promise.all( await Promise.all(
task.onMessageHandlers.map(async (onMessageHandler) => { task.onMessageHandlers.map(async (onMessageHandler) => {
await onMessageHandler(message); await onMessageHandler(message);
}) }),
); );
if (currentTask !== undefined && message.type === "result") { if (currentTask !== undefined && message.type === "result") {
console.log(
"[DEBUG startTask] 15. Result message received, pausing task"
);
this.upsertExistingTask({ this.upsertExistingTask({
...currentTask, ...currentTask,
status: "paused", status: "paused",
@@ -318,12 +311,12 @@ export class ClaudeCodeTaskController {
if (updatedTask === undefined) { if (updatedTask === undefined) {
console.log( console.log(
"[DEBUG startTask] 17. ERROR: Task not found in aliveTasks" "[DEBUG startTask] 17. ERROR: Task not found in aliveTasks",
); );
const error = new Error( const error = new Error(
`illegal state: task is not running, task: ${JSON.stringify( `illegal state: task is not running, task: ${JSON.stringify(
updatedTask updatedTask,
)}` )}`,
); );
aliveTaskReject(error); aliveTaskReject(error);
throw error; throw error;
@@ -336,7 +329,7 @@ export class ClaudeCodeTaskController {
} catch (error) { } catch (error) {
if (!resolved) { if (!resolved) {
console.log( console.log(
"[DEBUG startTask] 20. Rejecting task (not yet resolved)" "[DEBUG startTask] 20. Rejecting task (not yet resolved)",
); );
aliveTaskReject(error); aliveTaskReject(error);
resolved = true; resolved = true;

View File

@@ -10,7 +10,7 @@ class EventBus {
public emit<EventName extends keyof InternalEventDeclaration>( public emit<EventName extends keyof InternalEventDeclaration>(
event: EventName, event: EventName,
data: InternalEventDeclaration[EventName] data: InternalEventDeclaration[EventName],
): void { ): void {
this.emitter.emit(event, { this.emitter.emit(event, {
...data, ...data,
@@ -20,8 +20,8 @@ class EventBus {
public on<EventName extends keyof InternalEventDeclaration>( public on<EventName extends keyof InternalEventDeclaration>(
event: EventName, event: EventName,
listener: ( listener: (
data: InternalEventDeclaration[EventName] data: InternalEventDeclaration[EventName],
) => void | Promise<void> ) => void | Promise<void>,
): void { ): void {
this.emitter.on(event, listener); this.emitter.on(event, listener);
} }
@@ -29,8 +29,8 @@ class EventBus {
public off<EventName extends keyof InternalEventDeclaration>( public off<EventName extends keyof InternalEventDeclaration>(
event: EventName, event: EventName,
listener: ( listener: (
data: InternalEventDeclaration[EventName] data: InternalEventDeclaration[EventName],
) => void | Promise<void> ) => void | Promise<void>,
): void { ): void {
this.emitter.off(event, listener); this.emitter.off(event, listener);
} }