chore: disable permisseion setting if feature unavailable

This commit is contained in:
d-kimsuon
2025-10-26 21:26:44 +09:00
parent 51280f5bf8
commit 8b43b16522
12 changed files with 194 additions and 75 deletions

View File

@@ -10,6 +10,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useFeatureFlags } from "@/hooks/useFeatureFlags";
import { useTheme } from "@/hooks/useTheme";
import { projectDetailQuery, projectListQuery } from "../lib/api/queries";
import type { SupportedLocale } from "../lib/i18n/schema";
@@ -36,6 +37,9 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
const queryClient = useQueryClient();
const { theme } = useTheme();
const { i18n } = useLingui();
const { isFlagEnabled } = useFeatureFlags();
const isToolApprovalAvailable = isFlagEnabled("tool-approval");
const handleHideNoUserMessageChange = async () => {
const newConfig = {
@@ -222,6 +226,7 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
<Select
value={config?.permissionMode || "default"}
onValueChange={handlePermissionModeChange}
disabled={!isToolApprovalAvailable}
>
<SelectTrigger id={permissionModeId} className="w-full">
<SelectValue placeholder={i18n._("Select permission mode")} />
@@ -253,7 +258,7 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
</SelectItem>
</SelectContent>
</Select>
{showDescriptions && (
{showDescriptions && isToolApprovalAvailable && (
<p className="text-xs text-muted-foreground mt-1">
<Trans
id="settings.permission.mode.description"
@@ -261,6 +266,14 @@ export const SettingsControls: FC<SettingsControlsProps> = ({
/>
</p>
)}
{showDescriptions && !isToolApprovalAvailable && (
<p className="text-xs text-destructive mt-1">
<Trans
id="settings.permission.mode.unavailable"
message="This feature is not available in your Claude Code version. All tools will be automatically approved."
/>
</p>
)}
</div>
<div className="space-y-2">

View File

@@ -2,11 +2,8 @@ import { Trans } from "@lingui/react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { CheckCircle2, ChevronDown, ChevronRight, XCircle } from "lucide-react";
import { type FC, type ReactNode, useState } from "react";
import {
claudeCodeFeaturesQuery,
claudeCodeMetaQuery,
systemVersionQuery,
} from "@/lib/api/queries";
import { useFeatureFlags } from "@/hooks/useFeatureFlags";
import { claudeCodeMetaQuery, systemVersionQuery } from "@/lib/api/queries";
import { Badge } from "./ui/badge";
import {
Collapsible,
@@ -27,48 +24,33 @@ interface FeatureInfo {
const getFeatureInfo = (featureName: string): FeatureInfo => {
switch (featureName) {
case "canUseTool":
case "tool-approval":
return {
title: (
<Trans
id="system_info.feature.can_use_tool.title"
message="Tool Use Permission Control"
id="system_info.feature.tool_approval.title"
message="Tool Execution Approval"
/>
),
description: (
<Trans
id="system_info.feature.can_use_tool.description"
message="Dynamically control tool usage permissions and request user approval before tool execution (v1.0.82+)"
id="system_info.feature.tool_approval.description"
message="Allows you to approve or reject tool executions before Claude runs them, giving you full control over actions"
/>
),
};
case "uuidOnSDKMessage":
return {
title: (
<Trans
id="system_info.feature.uuid_on_sdk_message.title"
message="Message UUID Support"
/>
),
description: (
<Trans
id="system_info.feature.uuid_on_sdk_message.description"
message="Adds unique identifiers to SDK messages for better tracking (v1.0.86+)"
/>
),
};
case "agentSdk":
case "agent-sdk":
return {
title: (
<Trans
id="system_info.feature.agent_sdk.title"
message="Claude Agent SDK"
message="Enhanced Agent Mode"
/>
),
description: (
<Trans
id="system_info.feature.agent_sdk.description"
message="Uses Claude Agent SDK instead of Claude Code SDK (v1.0.125+)"
message="Uses @anthropic-ai/claude-agent-sdk instead of @anthropic-ai/claude-code for enhanced capabilities"
/>
),
};
@@ -96,9 +78,7 @@ export const SystemInfoCard: FC = () => {
...claudeCodeMetaQuery,
});
const { data: claudeCodeFeaturesData } = useSuspenseQuery({
...claudeCodeFeaturesQuery,
});
const { flags } = useFeatureFlags();
return (
<div className="h-full flex flex-col">
@@ -185,7 +165,7 @@ export const SystemInfoCard: FC = () => {
<CollapsibleContent className="pt-3">
<TooltipProvider>
<ul className="space-y-2 pl-2">
{claudeCodeFeaturesData?.features.map(({ name, enabled }) => {
{flags.map(({ name, enabled }) => {
const featureInfo = getFeatureInfo(name);
return (
<li key={name} className="flex items-start gap-2">

View File

@@ -0,0 +1,29 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { useCallback, useMemo } from "react";
import { featureFlagsQuery } from "../lib/api/queries";
import type { FlagName } from "../server/core/feature-flag/models/flag";
export const useFeatureFlags = () => {
const { data } = useSuspenseQuery({
queryKey: featureFlagsQuery.queryKey,
queryFn: featureFlagsQuery.queryFn,
});
const enabledFlags = useMemo(() => {
return new Set(
data.flags.filter((flag) => flag.enabled).map((flag) => flag.name),
);
}, [data.flags]);
const isFlagEnabled = useCallback(
(flagName: FlagName) => {
return enabledFlags.has(flagName);
},
[enabledFlags],
);
return {
flags: data.flags,
isFlagEnabled,
} as const;
};

View File

@@ -262,3 +262,16 @@ export const schedulerJobsQuery = {
return await response.json();
},
} as const;
export const featureFlagsQuery = {
queryKey: ["flags"],
queryFn: async () => {
const response = await honoClient.api.flags.$get();
if (!response.ok) {
throw new Error(`Failed to fetch feature flags: ${response.statusText}`);
}
return await response.json();
},
} as const;

View File

@@ -368,11 +368,11 @@
"translation": "Choose your preferred language"
},
"system_info.feature.agent_sdk.title": {
"message": "Claude Agent SDK",
"message": "Enhanced Agent Mode",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 65]],
"translation": "Claude Agent SDK"
"origin": [["src/components/SystemInfoCard.tsx", 49]],
"translation": "Enhanced Agent Mode"
},
"system_info.claude_code": {
"message": "Claude Code",
@@ -486,6 +486,13 @@
"origin": [["src/components/SettingsControls.tsx", 255]],
"translation": "Control how Claude Code handles permission requests for file operations"
},
"settings.permission.mode.unavailable": {
"message": "This feature is not available in your Claude Code version. All tools will be automatically approved.",
"placeholders": {},
"comments": [],
"origin": [["src/components/SettingsControls.tsx", 272]],
"translation": "This feature is not available in your Claude Code version. All tools will be automatically approved."
},
"session.conversation.in.progress": {
"message": "Conversation is in progress...",
"placeholders": {},
@@ -599,13 +606,6 @@
"origin": [["src/components/GlobalSidebar.tsx", 56]],
"translation": "Display and behavior preferences"
},
"system_info.feature.can_use_tool.description": {
"message": "Dynamically control tool usage permissions and request user approval before tool execution (v1.0.82+)",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 41]],
"translation": "Dynamically control tool usage permissions and request user approval before tool execution (v1.0.82+)"
},
"settings.locale.en": {
"message": "English",
"placeholders": {},
@@ -1506,13 +1506,6 @@
],
"translation": "Tool Result"
},
"system_info.feature.can_use_tool.title": {
"message": "Tool Use Permission Control",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 35]],
"translation": "Tool Use Permission Control"
},
"sessions.total": {
"message": "total",
"placeholders": {},
@@ -1629,11 +1622,25 @@
"translation": "Document type not supported for display"
},
"system_info.feature.agent_sdk.description": {
"message": "Uses Claude Agent SDK instead of Claude Code SDK (v1.0.125+)",
"message": "Uses @anthropic-ai/claude-agent-sdk instead of @anthropic-ai/claude-code for enhanced capabilities",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 71]],
"translation": "Uses Claude Agent SDK instead of Claude Code SDK (v1.0.125+)"
"origin": [["src/components/SystemInfoCard.tsx", 53]],
"translation": "Uses @anthropic-ai/claude-agent-sdk instead of @anthropic-ai/claude-code for enhanced capabilities"
},
"system_info.feature.tool_approval.title": {
"message": "Tool Execution Approval",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 35]],
"translation": "Tool Execution Approval"
},
"system_info.feature.tool_approval.description": {
"message": "Allows you to approve or reject tool executions before Claude runs them, giving you full control over actions",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 41]],
"translation": "Allows you to approve or reject tool executions before Claude runs them, giving you full control over actions"
},
"system_info.version_label": {
"message": "Version",

File diff suppressed because one or more lines are too long

View File

@@ -368,11 +368,11 @@
"translation": "お好みの言語を選択"
},
"system_info.feature.agent_sdk.title": {
"message": "Claude Agent SDK",
"message": "Enhanced Agent Mode",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 65]],
"translation": "Claude Agent SDK"
"origin": [["src/components/SystemInfoCard.tsx", 49]],
"translation": "拡張エージェントモード"
},
"system_info.claude_code": {
"message": "Claude Code",
@@ -486,6 +486,13 @@
"origin": [["src/components/SettingsControls.tsx", 255]],
"translation": "ファイル操作の権限リクエストの処理方法を制御"
},
"settings.permission.mode.unavailable": {
"message": "This feature is not available in your Claude Code version. All tools will be automatically approved.",
"placeholders": {},
"comments": [],
"origin": [["src/components/SettingsControls.tsx", 272]],
"translation": "お使いのClaude Codeバージョンではこの機能は利用できません。すべてのツールは自動で承認されます。"
},
"session.conversation.in.progress": {
"message": "Conversation is in progress...",
"placeholders": {},
@@ -599,13 +606,6 @@
"origin": [["src/components/GlobalSidebar.tsx", 56]],
"translation": "表示と動作の設定"
},
"system_info.feature.can_use_tool.description": {
"message": "Dynamically control tool usage permissions and request user approval before tool execution (v1.0.82+)",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 41]],
"translation": "動的にツールの使用許可を制御し、ツール実行前にユーザーの承認を求めることができます (v1.0.82+)"
},
"settings.locale.en": {
"message": "English",
"placeholders": {},
@@ -1506,13 +1506,6 @@
],
"translation": "ツール実行結果"
},
"system_info.feature.can_use_tool.title": {
"message": "Tool Use Permission Control",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 35]],
"translation": "ツール使用権限制御"
},
"sessions.total": {
"message": "total",
"placeholders": {},
@@ -1629,11 +1622,25 @@
"translation": "文書タイプは表示がサポートされていません"
},
"system_info.feature.agent_sdk.description": {
"message": "Uses Claude Agent SDK instead of Claude Code SDK (v1.0.125+)",
"message": "Uses @anthropic-ai/claude-agent-sdk instead of @anthropic-ai/claude-code for enhanced capabilities",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 71]],
"translation": "Claude Code SDKではなくClaude Agent SDKを使用 (v1.0.125+)"
"origin": [["src/components/SystemInfoCard.tsx", 53]],
"translation": "@anthropic-ai/claude-code ではなく @anthropic-ai/claude-agent-sdk を利用します"
},
"system_info.feature.tool_approval.title": {
"message": "Tool Execution Approval",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 35]],
"translation": "ツール実行承認"
},
"system_info.feature.tool_approval.description": {
"message": "Allows you to approve or reject tool executions before Claude runs them, giving you full control over actions",
"placeholders": {},
"comments": [],
"origin": [["src/components/SystemInfoCard.tsx", 41]],
"translation": "Claude がツールを実行する前に承認または拒否でき、アクションを完全にコントロールできます"
},
"system_info.version_label": {
"message": "Version",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
export type Flag =
| {
name: "tool-approval";
enabled: boolean;
}
| {
name: "agent-sdk";
enabled: boolean;
};
export type FlagName = Flag["name"];

View File

@@ -0,0 +1,43 @@
import { Context, Effect, Layer } from "effect";
import type { ControllerResponse } from "../../../lib/effect/toEffectResponse";
import type { InferEffect } from "../../../lib/effect/types";
import { ClaudeCodeService } from "../../claude-code/services/ClaudeCodeService";
import type { Flag } from "../models/flag";
const LayerImpl = Effect.gen(function* () {
const claudeCodeService = yield* ClaudeCodeService;
const getFlags = () =>
Effect.gen(function* () {
const claudeCodeFeatures =
yield* claudeCodeService.getAvailableFeatures();
return {
response: {
flags: [
{
name: "tool-approval",
enabled: claudeCodeFeatures.canUseTool,
},
{
name: "agent-sdk",
enabled: claudeCodeFeatures.agentSdk,
},
] satisfies Flag[],
},
status: 200,
} as const satisfies ControllerResponse;
});
return {
getFlags,
};
});
export type IFeatureFlagController = InferEffect<typeof LayerImpl>;
export class FeatureFlagController extends Context.Tag("FeatureFlagController")<
FeatureFlagController,
IFeatureFlagController
>() {
static Live = Layer.effect(this, LayerImpl);
}

View File

@@ -13,6 +13,7 @@ import { userMessageInputSchema } from "../core/claude-code/schema";
import { ClaudeCodeLifeCycleService } from "../core/claude-code/services/ClaudeCodeLifeCycleService";
import { TypeSafeSSE } from "../core/events/functions/typeSafeSSE";
import { SSEController } from "../core/events/presentation/SSEController";
import { FeatureFlagController } from "../core/feature-flag/presentation/FeatureFlagController";
import { FileSystemController } from "../core/file-system/presentation/FileSystemController";
import { GitController } from "../core/git/presentation/GitController";
import { CommitRequestSchema, PushRequestSchema } from "../core/git/schema";
@@ -49,6 +50,7 @@ export const routes = (app: HonoAppType) =>
const fileSystemController = yield* FileSystemController;
const claudeCodeController = yield* ClaudeCodeController;
const schedulerController = yield* SchedulerController;
const featureFlagController = yield* FeatureFlagController;
// services
const envService = yield* EnvService;
@@ -553,6 +555,18 @@ export const routes = (app: HonoAppType) =>
return response;
},
)
/**
* FeatureFlagController Routes
*/
.get("/api/flags", async (c) => {
const response = await effectToResponse(
c,
featureFlagController.getFlags().pipe(Effect.provide(runtime)),
);
return response;
})
);
});

View File

@@ -13,6 +13,7 @@ import { ClaudeCodeService } from "./core/claude-code/services/ClaudeCodeService
import { ClaudeCodeSessionProcessService } from "./core/claude-code/services/ClaudeCodeSessionProcessService";
import { SSEController } from "./core/events/presentation/SSEController";
import { FileWatcherService } from "./core/events/services/fileWatcher";
import { FeatureFlagController } from "./core/feature-flag/presentation/FeatureFlagController";
import { FileSystemController } from "./core/file-system/presentation/FileSystemController";
import { GitController } from "./core/git/presentation/GitController";
import { GitService } from "./core/git/services/GitService";
@@ -68,6 +69,7 @@ const program = routes(honoApp)
Effect.provide(FileSystemController.Live),
Effect.provide(SSEController.Live),
Effect.provide(SchedulerController.Live),
Effect.provide(FeatureFlagController.Live),
)
.pipe(
/** Application */