From 8d592ce89ba8bcb72fa1d3c1656fd591c7069485 Mon Sep 17 00:00:00 2001 From: d-kimsuon Date: Tue, 14 Oct 2025 12:18:29 +0900 Subject: [PATCH] fix: disable tool approve for old claude code version --- .../components/chatForm/useChatMutations.ts | 8 +- src/server/hono/route.ts | 16 ++- .../service/claude-code/ClaudeCodeExecutor.ts | 18 +-- .../claude-code/ClaudeCodeTaskController.ts | 110 +++++++----------- .../service/claude-code/ClaudeCodeVersion.ts | 4 - src/server/service/claude-code/types.ts | 3 + 6 files changed, 66 insertions(+), 93 deletions(-) diff --git a/src/app/projects/[projectId]/components/chatForm/useChatMutations.ts b/src/app/projects/[projectId]/components/chatForm/useChatMutations.ts index 72d49ab..f77a818 100644 --- a/src/app/projects/[projectId]/components/chatForm/useChatMutations.ts +++ b/src/app/projects/[projectId]/components/chatForm/useChatMutations.ts @@ -32,7 +32,13 @@ export const useNewChatMutation = ( }, onSuccess: async (response) => { onSuccess?.(); - router.push(`/projects/${projectId}/sessions/${response.sessionId}`); + router.push( + `/projects/${projectId}/sessions/${response.sessionId}` + + response.userMessageId !== + undefined + ? `#message-${response.userMessageId}` + : "", + ); }, }); }; diff --git a/src/server/hono/route.ts b/src/server/hono/route.ts index ac7a713..9521f74 100644 --- a/src/server/hono/route.ts +++ b/src/server/hono/route.ts @@ -4,9 +4,9 @@ import { zValidator } from "@hono/zod-validator"; import { setCookie } from "hono/cookie"; import { streamSSE } from "hono/streaming"; import { z } from "zod"; -import { configSchema } from "../config/config"; +import { type Config, configSchema } from "../config/config"; import { env } from "../lib/env"; -import { claudeCodeTaskController } from "../service/claude-code/ClaudeCodeTaskController"; +import { ClaudeCodeTaskController } from "../service/claude-code/ClaudeCodeTaskController"; import type { SerializableAliveTask } from "../service/claude-code/types"; import { adaptInternalEventToSSE } from "../service/events/adaptInternalEventToSSE"; import { eventBus } from "../service/events/EventBus"; @@ -28,11 +28,15 @@ export const routes = async (app: HonoAppType) => { const sessionRepository = new SessionRepository(); const projectRepository = new ProjectRepository(); + const fileWatcher = getFileWatcher(); + const eventBus = getEventBus(); + if (env.get("NEXT_PHASE") !== "phase-production-build") { - await initialize({ - sessionRepository, - projectRepository, - }); + fileWatcher.startWatching(); + + setInterval(() => { + eventBus.emit("heartbeat", {}); + }, 10 * 1000); } return ( diff --git a/src/server/service/claude-code/ClaudeCodeExecutor.ts b/src/server/service/claude-code/ClaudeCodeExecutor.ts index 55285b3..d3c1678 100644 --- a/src/server/service/claude-code/ClaudeCodeExecutor.ts +++ b/src/server/service/claude-code/ClaudeCodeExecutor.ts @@ -23,17 +23,13 @@ export class ClaudeCodeExecutor { ); } - public get version() { - return this.claudeCodeVersion?.version; - } - - public get availableFeatures() { + public get features() { return { - canUseTool: + enableToolApproval: this.claudeCodeVersion?.greaterThanOrEqual( new ClaudeCodeVersion({ major: 1, minor: 0, patch: 82 }), ) ?? false, - uuidOnSDKMessage: + extractUuidFromSDKMessage: this.claudeCodeVersion?.greaterThanOrEqual( new ClaudeCodeVersion({ major: 1, minor: 0, patch: 86 }), ) ?? false, @@ -41,18 +37,14 @@ export class ClaudeCodeExecutor { } public query(prompt: CCQueryPrompt, options: CCQueryOptions) { - const { canUseTool, permissionMode, ...baseOptions } = options; + const { canUseTool, ...baseOptions } = options; return query({ prompt, options: { pathToClaudeCodeExecutable: this.pathToClaudeCodeExecutable, ...baseOptions, - ...(this.availableFeatures.canUseTool - ? { canUseTool, permissionMode } - : { - permissionMode: "bypassPermissions", - }), + ...(this.features.enableToolApproval ? { canUseTool } : {}), }, }); } diff --git a/src/server/service/claude-code/ClaudeCodeTaskController.ts b/src/server/service/claude-code/ClaudeCodeTaskController.ts index 2e987d4..4883b31 100644 --- a/src/server/service/claude-code/ClaudeCodeTaskController.ts +++ b/src/server/service/claude-code/ClaudeCodeTaskController.ts @@ -1,10 +1,7 @@ -import { resolve } from "node:path"; +import prexit from "prexit"; import { ulid } from "ulid"; import type { Config } from "../../config/config"; -import { eventBus } from "../events/EventBus"; -import { parseCommandXml } from "../parseCommandXml"; -import { decodeProjectId } from "../project/id"; -import { predictSessionsDatabase } from "../session/PredictSessionsDatabase"; +import { getEventBus, type IEventBus } from "../events/EventBus"; import { ClaudeCodeExecutor } from "./ClaudeCodeExecutor"; import { createMessageGenerator } from "./createMessageGenerator"; import type { @@ -16,21 +13,23 @@ import type { RunningClaudeCodeTask, } from "./types"; -class ClaudeCodeTaskController { +export class ClaudeCodeTaskController { private claudeCode: ClaudeCodeExecutor; private tasks: ClaudeCodeTask[] = []; private config: Config; private pendingPermissionRequests: Map = new Map(); private permissionResponses: Map = new Map(); - constructor() { + constructor(config: Config) { this.claudeCode = new ClaudeCodeExecutor(); - this.config = { - hideNoUserMessageSession: false, - unifySameTitleSession: false, - enterKeyBehavior: "shift-enter-send", - permissionMode: "default", - }; + this.eventBus = getEventBus(); + this.config = config; + + prexit(() => { + this.aliveTasks.forEach((task) => { + task.abortController.abort(); + }); + }); } public updateConfig(config: Config) { @@ -170,20 +169,9 @@ class ClaudeCodeTaskController { ); if (existingTask) { - console.log( - `Alive task for session(id=${currentSession.sessionId}) continued.`, - ); const result = await this.continueTask(existingTask, message); return result; } else { - if (currentSession.sessionId === undefined) { - console.log(`New task started.`); - } else { - console.log( - `New task started for existing session(id=${currentSession.sessionId}).`, - ); - } - const result = await this.startTask(currentSession, message); return result; } @@ -265,41 +253,29 @@ class ClaudeCodeTaskController { }); } - if ( - message.type === "system" && - message.subtype === "init" && - currentSession.sessionId === undefined - ) { - // because it takes time for the Claude Code file to be updated, simulate the message - predictSessionsDatabase.createPredictSession({ - id: message.session_id, - jsonlFilePath: resolve( - decodeProjectId(currentSession.projectId), - `${message.session_id}.jsonl`, - ), - conversations: [ - { - type: "user", - message: { - role: "user", - content: userMessage, - }, - isSidechain: false, - userType: "external", - cwd: message.cwd, - sessionId: message.session_id, - version: this.claudeCode.version?.toString() ?? "unknown", - uuid: message.uuid, - timestamp: new Date().toISOString(), - parentUuid: null, - }, - ], - meta: { - firstCommand: parseCommandXml(userMessage), - messageCount: 0, - }, - lastModifiedAt: new Date(), - }); + // 初回の system message だとまだ history ファイルが作成されていないので + if (message.type === "user" || message.type === "assistant") { + // 本来は message.uuid の存在チェックをしたいが、古いバージョンでは存在しないことがある + if (!resolved) { + const runningTask: RunningClaudeCodeTask = { + status: "running", + id: task.id, + projectId: task.projectId, + cwd: task.cwd, + generateMessages: task.generateMessages, + setNextMessage: task.setNextMessage, + resolveFirstMessage: task.resolveFirstMessage, + setFirstMessagePromise: task.setFirstMessagePromise, + awaitFirstMessage: task.awaitFirstMessage, + onMessageHandlers: task.onMessageHandlers, + userMessageId: message.uuid, + sessionId: message.session_id, + abortController: abortController, + }; + this.tasks.push(runningTask); + aliveTaskResolve(runningTask); + resolved = true; + } eventBus.emit("sessionListChanged", { projectId: task.projectId, @@ -415,12 +391,6 @@ class ClaudeCodeTaskController { }); } - public abortAllTasks() { - for (const task of this.aliveTasks) { - task.abortController.abort(); - } - } - private upsertExistingTask(task: ClaudeCodeTask) { const target = this.tasks.find((t) => t.id === task.id); @@ -431,10 +401,12 @@ class ClaudeCodeTaskController { Object.assign(target, task); } - eventBus.emit("taskChanged", { - aliveTasks: this.aliveTasks, - changed: task, - }); + if (task.status === "paused" || task.status === "running") { + this.eventBus.emit("taskChanged", { + aliveTasks: this.aliveTasks, + changed: task, + }); + } } } diff --git a/src/server/service/claude-code/ClaudeCodeVersion.ts b/src/server/service/claude-code/ClaudeCodeVersion.ts index 9193a7e..db3b4f3 100644 --- a/src/server/service/claude-code/ClaudeCodeVersion.ts +++ b/src/server/service/claude-code/ClaudeCodeVersion.ts @@ -43,10 +43,6 @@ export class ClaudeCodeVersion { return this.version.patch; } - public toString() { - return `${this.major}.${this.minor}.${this.patch}`; - } - public equals(other: ClaudeCodeVersion) { return ( this.version.major === other.version.major && diff --git a/src/server/service/claude-code/types.ts b/src/server/service/claude-code/types.ts index c920d14..83889df 100644 --- a/src/server/service/claude-code/types.ts +++ b/src/server/service/claude-code/types.ts @@ -20,18 +20,21 @@ export type PendingClaudeCodeTask = BaseClaudeCodeTask & { export type RunningClaudeCodeTask = BaseClaudeCodeTask & { status: "running"; sessionId: string; + userMessageId: string | undefined; abortController: AbortController; }; export type PausedClaudeCodeTask = BaseClaudeCodeTask & { status: "paused"; sessionId: string; + userMessageId: string | undefined; abortController: AbortController; }; type CompletedClaudeCodeTask = BaseClaudeCodeTask & { status: "completed"; sessionId: string; + userMessageId: string | undefined; abortController: AbortController; resolveFirstMessage: () => void; };