@shareai-lab/kode 1.1.13 → 1.1.16-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoints/cli.js +59 -38
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/index.js +5 -26
- package/dist/package.json +4 -1
- package/package.json +11 -104
- package/dist/test/testAdapters.js +0 -88
- package/dist/test/testAdapters.js.map +0 -1
- package/src/ProjectOnboarding.tsx +0 -198
- package/src/Tool.ts +0 -83
- package/src/commands/agents.tsx +0 -3416
- package/src/commands/approvedTools.ts +0 -53
- package/src/commands/bug.tsx +0 -20
- package/src/commands/clear.ts +0 -43
- package/src/commands/compact.ts +0 -120
- package/src/commands/config.tsx +0 -19
- package/src/commands/cost.ts +0 -18
- package/src/commands/ctx_viz.ts +0 -209
- package/src/commands/doctor.ts +0 -24
- package/src/commands/help.tsx +0 -19
- package/src/commands/init.ts +0 -37
- package/src/commands/listen.ts +0 -42
- package/src/commands/login.tsx +0 -51
- package/src/commands/logout.tsx +0 -40
- package/src/commands/mcp.ts +0 -41
- package/src/commands/model.tsx +0 -40
- package/src/commands/modelstatus.tsx +0 -20
- package/src/commands/onboarding.tsx +0 -34
- package/src/commands/pr_comments.ts +0 -59
- package/src/commands/refreshCommands.ts +0 -54
- package/src/commands/release-notes.ts +0 -34
- package/src/commands/resume.tsx +0 -31
- package/src/commands/review.ts +0 -49
- package/src/commands/terminalSetup.ts +0 -221
- package/src/commands.ts +0 -139
- package/src/components/ApproveApiKey.tsx +0 -93
- package/src/components/AsciiLogo.tsx +0 -13
- package/src/components/AutoUpdater.tsx +0 -148
- package/src/components/Bug.tsx +0 -367
- package/src/components/Config.tsx +0 -293
- package/src/components/ConsoleOAuthFlow.tsx +0 -327
- package/src/components/Cost.tsx +0 -23
- package/src/components/CostThresholdDialog.tsx +0 -46
- package/src/components/CustomSelect/option-map.ts +0 -42
- package/src/components/CustomSelect/select-option.tsx +0 -78
- package/src/components/CustomSelect/select.tsx +0 -152
- package/src/components/CustomSelect/theme.ts +0 -45
- package/src/components/CustomSelect/use-select-state.ts +0 -414
- package/src/components/CustomSelect/use-select.ts +0 -35
- package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
- package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
- package/src/components/Help.tsx +0 -215
- package/src/components/HighlightedCode.tsx +0 -33
- package/src/components/InvalidConfigDialog.tsx +0 -113
- package/src/components/Link.tsx +0 -32
- package/src/components/LogSelector.tsx +0 -86
- package/src/components/Logo.tsx +0 -170
- package/src/components/MCPServerApprovalDialog.tsx +0 -100
- package/src/components/MCPServerDialogCopy.tsx +0 -25
- package/src/components/MCPServerMultiselectDialog.tsx +0 -109
- package/src/components/Message.tsx +0 -221
- package/src/components/MessageResponse.tsx +0 -15
- package/src/components/MessageSelector.tsx +0 -211
- package/src/components/ModeIndicator.tsx +0 -88
- package/src/components/ModelConfig.tsx +0 -301
- package/src/components/ModelListManager.tsx +0 -227
- package/src/components/ModelSelector.tsx +0 -3387
- package/src/components/ModelStatusDisplay.tsx +0 -230
- package/src/components/Onboarding.tsx +0 -274
- package/src/components/PressEnterToContinue.tsx +0 -11
- package/src/components/PromptInput.tsx +0 -760
- package/src/components/SentryErrorBoundary.ts +0 -39
- package/src/components/Spinner.tsx +0 -129
- package/src/components/StickerRequestForm.tsx +0 -16
- package/src/components/StructuredDiff.tsx +0 -191
- package/src/components/TextInput.tsx +0 -259
- package/src/components/TodoItem.tsx +0 -47
- package/src/components/TokenWarning.tsx +0 -31
- package/src/components/ToolUseLoader.tsx +0 -40
- package/src/components/TrustDialog.tsx +0 -106
- package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
- package/src/components/binary-feedback/utils.ts +0 -220
- package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
- package/src/components/messages/AssistantTextMessage.tsx +0 -144
- package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
- package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
- package/src/components/messages/TaskProgressMessage.tsx +0 -32
- package/src/components/messages/TaskToolMessage.tsx +0 -58
- package/src/components/messages/UserBashInputMessage.tsx +0 -28
- package/src/components/messages/UserCommandMessage.tsx +0 -30
- package/src/components/messages/UserKodingInputMessage.tsx +0 -28
- package/src/components/messages/UserPromptMessage.tsx +0 -35
- package/src/components/messages/UserTextMessage.tsx +0 -39
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
- package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
- package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
- package/src/components/permissions/PermissionRequest.tsx +0 -101
- package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
- package/src/components/permissions/hooks.ts +0 -44
- package/src/components/permissions/toolUseOptions.ts +0 -59
- package/src/components/permissions/utils.ts +0 -23
- package/src/constants/betas.ts +0 -5
- package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
- package/src/constants/figures.ts +0 -4
- package/src/constants/keys.ts +0 -3
- package/src/constants/macros.ts +0 -11
- package/src/constants/modelCapabilities.ts +0 -179
- package/src/constants/models.ts +0 -1025
- package/src/constants/oauth.ts +0 -18
- package/src/constants/product.ts +0 -17
- package/src/constants/prompts.ts +0 -168
- package/src/constants/releaseNotes.ts +0 -7
- package/src/context/PermissionContext.tsx +0 -149
- package/src/context.ts +0 -278
- package/src/cost-tracker.ts +0 -84
- package/src/entrypoints/cli.tsx +0 -1561
- package/src/entrypoints/mcp.ts +0 -175
- package/src/history.ts +0 -25
- package/src/hooks/useApiKeyVerification.ts +0 -59
- package/src/hooks/useArrowKeyHistory.ts +0 -55
- package/src/hooks/useCanUseTool.ts +0 -138
- package/src/hooks/useCancelRequest.ts +0 -39
- package/src/hooks/useDoublePress.ts +0 -41
- package/src/hooks/useExitOnCtrlCD.ts +0 -31
- package/src/hooks/useInterval.ts +0 -25
- package/src/hooks/useLogMessages.ts +0 -16
- package/src/hooks/useLogStartupTime.ts +0 -12
- package/src/hooks/useNotifyAfterTimeout.ts +0 -65
- package/src/hooks/usePermissionRequestLogging.ts +0 -44
- package/src/hooks/useTerminalSize.ts +0 -49
- package/src/hooks/useTextInput.ts +0 -317
- package/src/hooks/useUnifiedCompletion.ts +0 -1405
- package/src/index.ts +0 -34
- package/src/messages.ts +0 -38
- package/src/permissions.ts +0 -268
- package/src/query.ts +0 -720
- package/src/screens/ConfigureNpmPrefix.tsx +0 -197
- package/src/screens/Doctor.tsx +0 -219
- package/src/screens/LogList.tsx +0 -68
- package/src/screens/REPL.tsx +0 -813
- package/src/screens/ResumeConversation.tsx +0 -68
- package/src/services/adapters/base.ts +0 -38
- package/src/services/adapters/chatCompletions.ts +0 -90
- package/src/services/adapters/responsesAPI.ts +0 -170
- package/src/services/browserMocks.ts +0 -66
- package/src/services/claude.ts +0 -2197
- package/src/services/customCommands.ts +0 -704
- package/src/services/fileFreshness.ts +0 -377
- package/src/services/gpt5ConnectionTest.ts +0 -340
- package/src/services/mcpClient.ts +0 -564
- package/src/services/mcpServerApproval.tsx +0 -50
- package/src/services/mentionProcessor.ts +0 -273
- package/src/services/modelAdapterFactory.ts +0 -69
- package/src/services/notifier.ts +0 -40
- package/src/services/oauth.ts +0 -357
- package/src/services/openai.ts +0 -1359
- package/src/services/responseStateManager.ts +0 -90
- package/src/services/sentry.ts +0 -3
- package/src/services/statsig.ts +0 -172
- package/src/services/statsigStorage.ts +0 -86
- package/src/services/systemReminder.ts +0 -507
- package/src/services/vcr.ts +0 -161
- package/src/test/testAdapters.ts +0 -96
- package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
- package/src/tools/ArchitectTool/prompt.ts +0 -15
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
- package/src/tools/BashTool/BashTool.tsx +0 -243
- package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
- package/src/tools/BashTool/OutputLine.tsx +0 -49
- package/src/tools/BashTool/prompt.ts +0 -174
- package/src/tools/BashTool/utils.ts +0 -56
- package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
- package/src/tools/FileEditTool/prompt.ts +0 -51
- package/src/tools/FileEditTool/utils.ts +0 -58
- package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
- package/src/tools/FileReadTool/prompt.ts +0 -7
- package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
- package/src/tools/FileWriteTool/prompt.ts +0 -10
- package/src/tools/GlobTool/GlobTool.tsx +0 -119
- package/src/tools/GlobTool/prompt.ts +0 -8
- package/src/tools/GrepTool/GrepTool.tsx +0 -147
- package/src/tools/GrepTool/prompt.ts +0 -11
- package/src/tools/MCPTool/MCPTool.tsx +0 -107
- package/src/tools/MCPTool/prompt.ts +0 -3
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
- package/src/tools/MemoryReadTool/prompt.ts +0 -3
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
- package/src/tools/MemoryWriteTool/prompt.ts +0 -3
- package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
- package/src/tools/MultiEditTool/prompt.ts +0 -45
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
- package/src/tools/NotebookEditTool/prompt.ts +0 -3
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
- package/src/tools/NotebookReadTool/prompt.ts +0 -3
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
- package/src/tools/StickerRequestTool/prompt.ts +0 -19
- package/src/tools/TaskTool/TaskTool.tsx +0 -438
- package/src/tools/TaskTool/constants.ts +0 -1
- package/src/tools/TaskTool/prompt.ts +0 -92
- package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
- package/src/tools/ThinkTool/prompt.ts +0 -12
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
- package/src/tools/TodoWriteTool/prompt.ts +0 -63
- package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
- package/src/tools/URLFetcherTool/cache.ts +0 -55
- package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
- package/src/tools/URLFetcherTool/prompt.ts +0 -17
- package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
- package/src/tools/WebSearchTool/prompt.ts +0 -13
- package/src/tools/WebSearchTool/searchProviders.ts +0 -66
- package/src/tools/lsTool/lsTool.tsx +0 -272
- package/src/tools/lsTool/prompt.ts +0 -2
- package/src/tools.ts +0 -67
- package/src/types/PermissionMode.ts +0 -120
- package/src/types/RequestContext.ts +0 -72
- package/src/types/common.d.ts +0 -2
- package/src/types/conversation.ts +0 -51
- package/src/types/logs.ts +0 -58
- package/src/types/modelCapabilities.ts +0 -64
- package/src/types/notebook.ts +0 -87
- package/src/utils/Cursor.ts +0 -436
- package/src/utils/PersistentShell.ts +0 -552
- package/src/utils/advancedFuzzyMatcher.ts +0 -290
- package/src/utils/agentLoader.ts +0 -278
- package/src/utils/agentStorage.ts +0 -97
- package/src/utils/array.ts +0 -3
- package/src/utils/ask.tsx +0 -99
- package/src/utils/auth.ts +0 -13
- package/src/utils/autoCompactCore.ts +0 -223
- package/src/utils/autoUpdater.ts +0 -458
- package/src/utils/betas.ts +0 -20
- package/src/utils/browser.ts +0 -14
- package/src/utils/cleanup.ts +0 -72
- package/src/utils/commands.ts +0 -261
- package/src/utils/commonUnixCommands.ts +0 -161
- package/src/utils/config.ts +0 -945
- package/src/utils/conversationRecovery.ts +0 -55
- package/src/utils/debugLogger.ts +0 -1235
- package/src/utils/diff.ts +0 -42
- package/src/utils/env.ts +0 -57
- package/src/utils/errors.ts +0 -21
- package/src/utils/exampleCommands.ts +0 -109
- package/src/utils/execFileNoThrow.ts +0 -51
- package/src/utils/expertChatStorage.ts +0 -136
- package/src/utils/file.ts +0 -405
- package/src/utils/fileRecoveryCore.ts +0 -71
- package/src/utils/format.tsx +0 -44
- package/src/utils/fuzzyMatcher.ts +0 -328
- package/src/utils/generators.ts +0 -62
- package/src/utils/git.ts +0 -92
- package/src/utils/globalLogger.ts +0 -77
- package/src/utils/http.ts +0 -10
- package/src/utils/imagePaste.ts +0 -38
- package/src/utils/json.ts +0 -13
- package/src/utils/log.ts +0 -382
- package/src/utils/markdown.ts +0 -213
- package/src/utils/messageContextManager.ts +0 -294
- package/src/utils/messages.tsx +0 -945
- package/src/utils/model.ts +0 -914
- package/src/utils/permissions/filesystem.ts +0 -127
- package/src/utils/responseState.ts +0 -23
- package/src/utils/ripgrep.ts +0 -167
- package/src/utils/secureFile.ts +0 -564
- package/src/utils/sessionState.ts +0 -49
- package/src/utils/state.ts +0 -25
- package/src/utils/style.ts +0 -29
- package/src/utils/terminal.ts +0 -50
- package/src/utils/theme.ts +0 -127
- package/src/utils/thinking.ts +0 -144
- package/src/utils/todoStorage.ts +0 -431
- package/src/utils/tokens.ts +0 -43
- package/src/utils/toolExecutionController.ts +0 -163
- package/src/utils/unaryLogging.ts +0 -26
- package/src/utils/user.ts +0 -37
- package/src/utils/validate.ts +0 -165
package/src/commands/agents.tsx
DELETED
|
@@ -1,3416 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo, useCallback, useReducer, Fragment } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import InkTextInput from 'ink-text-input'
|
|
4
|
-
import { getActiveAgents, clearAgentCache } from '../utils/agentLoader'
|
|
5
|
-
import { AgentConfig } from '../utils/agentLoader'
|
|
6
|
-
import { writeFileSync, unlinkSync, mkdirSync, existsSync, readFileSync, renameSync } from 'fs'
|
|
7
|
-
import { join } from 'path'
|
|
8
|
-
import * as path from 'path'
|
|
9
|
-
import { homedir } from 'os'
|
|
10
|
-
import * as os from 'os'
|
|
11
|
-
import { getCwd } from '../utils/state'
|
|
12
|
-
import { getTheme } from '../utils/theme'
|
|
13
|
-
import matter from 'gray-matter'
|
|
14
|
-
import { exec, spawn } from 'child_process'
|
|
15
|
-
import { promisify } from 'util'
|
|
16
|
-
import { watch, FSWatcher } from 'fs'
|
|
17
|
-
import { getMCPTools } from '../services/mcpClient'
|
|
18
|
-
import { getModelManager } from '../utils/model'
|
|
19
|
-
import { randomUUID } from 'crypto'
|
|
20
|
-
|
|
21
|
-
const execAsync = promisify(exec)
|
|
22
|
-
|
|
23
|
-
// Core constants aligned with Claude Code architecture
|
|
24
|
-
const AGENT_LOCATIONS = {
|
|
25
|
-
USER: "user",
|
|
26
|
-
PROJECT: "project",
|
|
27
|
-
BUILT_IN: "built-in",
|
|
28
|
-
ALL: "all"
|
|
29
|
-
} as const
|
|
30
|
-
|
|
31
|
-
const UI_ICONS = {
|
|
32
|
-
pointer: "❯",
|
|
33
|
-
checkboxOn: "☑",
|
|
34
|
-
checkboxOff: "☐",
|
|
35
|
-
warning: "⚠",
|
|
36
|
-
separator: "─",
|
|
37
|
-
loading: "◐◑◒◓"
|
|
38
|
-
} as const
|
|
39
|
-
|
|
40
|
-
const FOLDER_CONFIG = {
|
|
41
|
-
FOLDER_NAME: ".claude",
|
|
42
|
-
AGENTS_DIR: "agents"
|
|
43
|
-
} as const
|
|
44
|
-
|
|
45
|
-
// Tool categories for sophisticated selection
|
|
46
|
-
const TOOL_CATEGORIES = {
|
|
47
|
-
read: ['Read', 'Glob', 'Grep', 'LS'],
|
|
48
|
-
edit: ['Edit', 'MultiEdit', 'Write', 'NotebookEdit'],
|
|
49
|
-
execution: ['Bash', 'BashOutput', 'KillBash'],
|
|
50
|
-
web: ['WebFetch', 'WebSearch'],
|
|
51
|
-
other: ['TodoWrite', 'ExitPlanMode', 'Task']
|
|
52
|
-
} as const
|
|
53
|
-
|
|
54
|
-
type AgentLocation = typeof AGENT_LOCATIONS[keyof typeof AGENT_LOCATIONS]
|
|
55
|
-
|
|
56
|
-
// Models will be listed dynamically from ModelManager
|
|
57
|
-
|
|
58
|
-
// Comprehensive mode state for complete UI flow
|
|
59
|
-
type ModeState = {
|
|
60
|
-
mode: 'list-agents' | 'create-location' | 'create-method' | 'create-generate' | 'create-type' |
|
|
61
|
-
'create-description' | 'create-tools' | 'create-model' | 'create-color' | 'create-prompt' | 'create-confirm' |
|
|
62
|
-
'agent-menu' | 'view-agent' | 'edit-agent' | 'edit-tools' | 'edit-model' | 'edit-color' | 'delete-confirm'
|
|
63
|
-
location?: AgentLocation
|
|
64
|
-
selectedAgent?: AgentConfig
|
|
65
|
-
previousMode?: ModeState
|
|
66
|
-
[key: string]: any
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// State for agent creation flow
|
|
70
|
-
type CreateState = {
|
|
71
|
-
location: AgentLocation | null
|
|
72
|
-
agentType: string
|
|
73
|
-
method: 'generate' | 'manual' | null
|
|
74
|
-
generationPrompt: string
|
|
75
|
-
whenToUse: string
|
|
76
|
-
selectedTools: string[]
|
|
77
|
-
selectedModel: string | null // null for inherit, or model profile modelName
|
|
78
|
-
selectedColor: string | null
|
|
79
|
-
systemPrompt: string
|
|
80
|
-
isGenerating: boolean
|
|
81
|
-
wasGenerated: boolean
|
|
82
|
-
isAIGenerated: boolean
|
|
83
|
-
error: string | null
|
|
84
|
-
warnings: string[]
|
|
85
|
-
// Cursor positions for text inputs
|
|
86
|
-
agentTypeCursor: number
|
|
87
|
-
whenToUseCursor: number
|
|
88
|
-
promptCursor: number
|
|
89
|
-
generationPromptCursor: number
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
type Tool = {
|
|
93
|
-
name: string
|
|
94
|
-
description?: string | (() => Promise<string>)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Map a stored model identifier to a display name via ModelManager
|
|
98
|
-
function getDisplayModelName(modelId?: string | null): string {
|
|
99
|
-
// null/undefined means inherit from parent (task model)
|
|
100
|
-
if (!modelId) return 'Inherit'
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const profiles = getModelManager().getActiveModelProfiles()
|
|
104
|
-
const profile = profiles.find((p: any) => p.modelName === modelId || p.name === modelId)
|
|
105
|
-
return profile ? profile.name : `Custom (${modelId})`
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.warn('Failed to get model profiles:', error)
|
|
108
|
-
return modelId ? `Custom (${modelId})` : 'Inherit'
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// AI Generation response type
|
|
113
|
-
type GeneratedAgent = {
|
|
114
|
-
identifier: string
|
|
115
|
-
whenToUse: string
|
|
116
|
-
systemPrompt: string
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// AI generation function (use main pointer model)
|
|
120
|
-
async function generateAgentWithClaude(prompt: string): Promise<GeneratedAgent> {
|
|
121
|
-
// Import Claude service dynamically to avoid circular dependencies
|
|
122
|
-
const { queryModel } = await import('../services/claude')
|
|
123
|
-
|
|
124
|
-
const systemPrompt = `You are an expert at creating AI agent configurations. Based on the user's description, generate a specialized agent configuration.
|
|
125
|
-
|
|
126
|
-
Return your response as a JSON object with exactly these fields:
|
|
127
|
-
- identifier: A short, kebab-case identifier for the agent (e.g., "code-reviewer", "security-auditor")
|
|
128
|
-
- whenToUse: A clear description of when this agent should be used (50-200 words)
|
|
129
|
-
- systemPrompt: A comprehensive system prompt that defines the agent's role, capabilities, and behavior (200-500 words)
|
|
130
|
-
|
|
131
|
-
Make the agent highly specialized and effective for the described use case.`
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const messages = [
|
|
135
|
-
{
|
|
136
|
-
type: 'user',
|
|
137
|
-
uuid: randomUUID(),
|
|
138
|
-
message: { role: 'user', content: prompt },
|
|
139
|
-
},
|
|
140
|
-
] as any
|
|
141
|
-
const response = await queryModel('main', messages, [systemPrompt])
|
|
142
|
-
|
|
143
|
-
// Get the text content from the response - handle both string and object responses
|
|
144
|
-
let responseText = ''
|
|
145
|
-
if (typeof response.message?.content === 'string') {
|
|
146
|
-
responseText = response.message.content
|
|
147
|
-
} else if (Array.isArray(response.message?.content)) {
|
|
148
|
-
const textContent = response.message.content.find((c: any) => c.type === 'text')
|
|
149
|
-
responseText = textContent?.text || ''
|
|
150
|
-
} else if (response.message?.content?.[0]?.text) {
|
|
151
|
-
responseText = response.message.content[0].text
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!responseText) {
|
|
155
|
-
throw new Error('No text content in Claude response')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// 安全限制
|
|
159
|
-
const MAX_JSON_SIZE = 100_000 // 100KB
|
|
160
|
-
const MAX_FIELD_LENGTH = 10_000
|
|
161
|
-
|
|
162
|
-
if (responseText.length > MAX_JSON_SIZE) {
|
|
163
|
-
throw new Error('Response too large')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 安全的JSON提取和解析
|
|
167
|
-
let parsed: any
|
|
168
|
-
try {
|
|
169
|
-
// 首先尝试直接解析整个响应
|
|
170
|
-
parsed = JSON.parse(responseText.trim())
|
|
171
|
-
} catch {
|
|
172
|
-
// 如果失败,提取第一个JSON对象,限制搜索范围
|
|
173
|
-
const startIdx = responseText.indexOf('{')
|
|
174
|
-
const endIdx = responseText.lastIndexOf('}')
|
|
175
|
-
|
|
176
|
-
if (startIdx === -1 || endIdx === -1 || startIdx >= endIdx) {
|
|
177
|
-
throw new Error('No valid JSON found in Claude response')
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const jsonStr = responseText.substring(startIdx, endIdx + 1)
|
|
181
|
-
if (jsonStr.length > MAX_JSON_SIZE) {
|
|
182
|
-
throw new Error('JSON content too large')
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
parsed = JSON.parse(jsonStr)
|
|
187
|
-
} catch (parseError) {
|
|
188
|
-
throw new Error(`Invalid JSON format: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 深度验证和安全清理
|
|
193
|
-
const identifier = String(parsed.identifier || '').slice(0, 100).trim()
|
|
194
|
-
const whenToUse = String(parsed.whenToUse || '').slice(0, MAX_FIELD_LENGTH).trim()
|
|
195
|
-
const agentSystemPrompt = String(parsed.systemPrompt || '').slice(0, MAX_FIELD_LENGTH).trim()
|
|
196
|
-
|
|
197
|
-
// 验证必填字段
|
|
198
|
-
if (!identifier || !whenToUse || !agentSystemPrompt) {
|
|
199
|
-
throw new Error('Invalid response structure: missing required fields (identifier, whenToUse, systemPrompt)')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 清理危险字符(控制字符和非打印字符)
|
|
203
|
-
const sanitize = (str: string) => str.replace(/[\x00-\x1F\x7F-\x9F]/g, '')
|
|
204
|
-
|
|
205
|
-
// 验证identifier格式(只允许字母、数字、连字符)
|
|
206
|
-
const cleanIdentifier = sanitize(identifier)
|
|
207
|
-
if (!/^[a-zA-Z0-9-]+$/.test(cleanIdentifier)) {
|
|
208
|
-
throw new Error('Invalid identifier format: only letters, numbers, and hyphens allowed')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
identifier: cleanIdentifier,
|
|
213
|
-
whenToUse: sanitize(whenToUse),
|
|
214
|
-
systemPrompt: sanitize(agentSystemPrompt)
|
|
215
|
-
}
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error('AI generation failed:', error)
|
|
218
|
-
// Fallback to a reasonable default based on the prompt
|
|
219
|
-
const fallbackId = prompt.toLowerCase()
|
|
220
|
-
.replace(/[^a-z0-9\s-]/g, '')
|
|
221
|
-
.replace(/\s+/g, '-')
|
|
222
|
-
.slice(0, 30)
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
identifier: fallbackId || 'custom-agent',
|
|
226
|
-
whenToUse: `Use this agent when you need assistance with: ${prompt}`,
|
|
227
|
-
systemPrompt: `You are a specialized assistant focused on helping with ${prompt}. Provide expert-level assistance in this domain.`
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Comprehensive validation system
|
|
233
|
-
function validateAgentType(agentType: string, existingAgents: AgentConfig[] = []): {
|
|
234
|
-
isValid: boolean
|
|
235
|
-
errors: string[]
|
|
236
|
-
warnings: string[]
|
|
237
|
-
} {
|
|
238
|
-
const errors: string[] = []
|
|
239
|
-
const warnings: string[] = []
|
|
240
|
-
|
|
241
|
-
if (!agentType) {
|
|
242
|
-
errors.push("Agent type is required")
|
|
243
|
-
return { isValid: false, errors, warnings }
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (!/^[a-zA-Z]/.test(agentType)) {
|
|
247
|
-
errors.push("Agent type must start with a letter")
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!/^[a-zA-Z0-9-]+$/.test(agentType)) {
|
|
251
|
-
errors.push("Agent type can only contain letters, numbers, and hyphens")
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (agentType.length < 3) {
|
|
255
|
-
errors.push("Agent type must be at least 3 characters long")
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (agentType.length > 50) {
|
|
259
|
-
errors.push("Agent type must be less than 50 characters")
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Check for reserved names
|
|
263
|
-
const reserved = ['help', 'exit', 'quit', 'agents', 'task']
|
|
264
|
-
if (reserved.includes(agentType.toLowerCase())) {
|
|
265
|
-
errors.push("This name is reserved")
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Check for duplicates
|
|
269
|
-
const duplicate = existingAgents.find(a => a.agentType === agentType)
|
|
270
|
-
if (duplicate) {
|
|
271
|
-
errors.push(`An agent with this name already exists in ${duplicate.location}`)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Warnings
|
|
275
|
-
if (agentType.includes('--')) {
|
|
276
|
-
warnings.push("Consider avoiding consecutive hyphens")
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
isValid: errors.length === 0,
|
|
281
|
-
errors,
|
|
282
|
-
warnings
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function validateAgentConfig(config: Partial<CreateState>, existingAgents: AgentConfig[] = []): {
|
|
287
|
-
isValid: boolean
|
|
288
|
-
errors: string[]
|
|
289
|
-
warnings: string[]
|
|
290
|
-
} {
|
|
291
|
-
const errors: string[] = []
|
|
292
|
-
const warnings: string[] = []
|
|
293
|
-
|
|
294
|
-
// Validate agent type
|
|
295
|
-
if (config.agentType) {
|
|
296
|
-
const typeValidation = validateAgentType(config.agentType, existingAgents)
|
|
297
|
-
errors.push(...typeValidation.errors)
|
|
298
|
-
warnings.push(...typeValidation.warnings)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Validate description
|
|
302
|
-
if (!config.whenToUse) {
|
|
303
|
-
errors.push("Description is required")
|
|
304
|
-
} else if (config.whenToUse.length < 10) {
|
|
305
|
-
warnings.push("Description should be more descriptive (at least 10 characters)")
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Validate system prompt
|
|
309
|
-
if (!config.systemPrompt) {
|
|
310
|
-
errors.push("System prompt is required")
|
|
311
|
-
} else if (config.systemPrompt.length < 20) {
|
|
312
|
-
warnings.push("System prompt might be too short for effective agent behavior")
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Validate tools
|
|
316
|
-
if (!config.selectedTools || config.selectedTools.length === 0) {
|
|
317
|
-
warnings.push("No tools selected - agent will have limited capabilities")
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return {
|
|
321
|
-
isValid: errors.length === 0,
|
|
322
|
-
errors,
|
|
323
|
-
warnings
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// File system operations with Claude Code alignment
|
|
328
|
-
function getAgentDirectory(location: AgentLocation): string {
|
|
329
|
-
if (location === AGENT_LOCATIONS.BUILT_IN || location === AGENT_LOCATIONS.ALL) {
|
|
330
|
-
throw new Error(`Cannot get directory path for ${location} agents`)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (location === AGENT_LOCATIONS.USER) {
|
|
334
|
-
return join(homedir(), FOLDER_CONFIG.FOLDER_NAME, FOLDER_CONFIG.AGENTS_DIR)
|
|
335
|
-
} else {
|
|
336
|
-
return join(getCwd(), FOLDER_CONFIG.FOLDER_NAME, FOLDER_CONFIG.AGENTS_DIR)
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function getAgentFilePath(agent: AgentConfig): string {
|
|
341
|
-
if (agent.location === 'built-in') {
|
|
342
|
-
throw new Error('Cannot get file path for built-in agents')
|
|
343
|
-
}
|
|
344
|
-
const dir = getAgentDirectory(agent.location as AgentLocation)
|
|
345
|
-
return join(dir, `${agent.agentType}.md`)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function ensureDirectoryExists(location: AgentLocation): string {
|
|
349
|
-
const dir = getAgentDirectory(location)
|
|
350
|
-
if (!existsSync(dir)) {
|
|
351
|
-
mkdirSync(dir, { recursive: true })
|
|
352
|
-
}
|
|
353
|
-
return dir
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Generate agent file content
|
|
357
|
-
function generateAgentFileContent(
|
|
358
|
-
agentType: string,
|
|
359
|
-
description: string,
|
|
360
|
-
tools: string[] | '*',
|
|
361
|
-
systemPrompt: string,
|
|
362
|
-
model?: string,
|
|
363
|
-
color?: string
|
|
364
|
-
): string {
|
|
365
|
-
// Use YAML multi-line string for description to avoid escaping issues
|
|
366
|
-
const descriptionLines = description.split('\n')
|
|
367
|
-
const formattedDescription = descriptionLines.length > 1
|
|
368
|
-
? `|\n ${descriptionLines.join('\n ')}`
|
|
369
|
-
: JSON.stringify(description)
|
|
370
|
-
|
|
371
|
-
const lines = [
|
|
372
|
-
'---',
|
|
373
|
-
`name: ${agentType}`,
|
|
374
|
-
`description: ${formattedDescription}`
|
|
375
|
-
]
|
|
376
|
-
|
|
377
|
-
if (tools) {
|
|
378
|
-
if (tools === '*') {
|
|
379
|
-
lines.push(`tools: "*"`)
|
|
380
|
-
} else if (Array.isArray(tools) && tools.length > 0) {
|
|
381
|
-
lines.push(`tools: [${tools.map(t => `"${t}"`).join(', ')}]`)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (model) {
|
|
386
|
-
lines.push(`model: ${model}`)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (color) {
|
|
390
|
-
lines.push(`color: ${color}`)
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
lines.push('---', '', systemPrompt)
|
|
394
|
-
return lines.join('\n')
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Save agent to file
|
|
398
|
-
async function saveAgent(
|
|
399
|
-
location: AgentLocation,
|
|
400
|
-
agentType: string,
|
|
401
|
-
description: string,
|
|
402
|
-
tools: string[],
|
|
403
|
-
systemPrompt: string,
|
|
404
|
-
model?: string,
|
|
405
|
-
color?: string,
|
|
406
|
-
throwIfExists: boolean = true
|
|
407
|
-
): Promise<void> {
|
|
408
|
-
if (location === AGENT_LOCATIONS.BUILT_IN) {
|
|
409
|
-
throw new Error('Cannot save built-in agents')
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
ensureDirectoryExists(location)
|
|
413
|
-
|
|
414
|
-
const filePath = join(getAgentDirectory(location), `${agentType}.md`)
|
|
415
|
-
const tempFile = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`
|
|
416
|
-
|
|
417
|
-
// Ensure tools is properly typed for file saving
|
|
418
|
-
const toolsForFile: string[] | '*' = Array.isArray(tools) && tools.length === 1 && tools[0] === '*' ? '*' : tools
|
|
419
|
-
const content = generateAgentFileContent(agentType, description, toolsForFile, systemPrompt, model, color)
|
|
420
|
-
|
|
421
|
-
try {
|
|
422
|
-
// 先写入临时文件,使用 'wx' 确保不覆盖现有文件
|
|
423
|
-
writeFileSync(tempFile, content, { encoding: 'utf-8', flag: 'wx' })
|
|
424
|
-
|
|
425
|
-
// 检查目标文件是否存在(原子性检查)
|
|
426
|
-
if (throwIfExists && existsSync(filePath)) {
|
|
427
|
-
// 清理临时文件
|
|
428
|
-
try { unlinkSync(tempFile) } catch {}
|
|
429
|
-
throw new Error(`Agent file already exists: ${filePath}`)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// 原子性重命名(在大多数文件系统上,rename是原子操作)
|
|
433
|
-
renameSync(tempFile, filePath)
|
|
434
|
-
|
|
435
|
-
} catch (error) {
|
|
436
|
-
// 确保清理临时文件
|
|
437
|
-
try {
|
|
438
|
-
if (existsSync(tempFile)) {
|
|
439
|
-
unlinkSync(tempFile)
|
|
440
|
-
}
|
|
441
|
-
} catch (cleanupError) {
|
|
442
|
-
console.warn('Failed to cleanup temp file:', cleanupError)
|
|
443
|
-
}
|
|
444
|
-
throw error
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Delete agent file
|
|
449
|
-
async function deleteAgent(agent: AgentConfig): Promise<void> {
|
|
450
|
-
if (agent.location === 'built-in') {
|
|
451
|
-
throw new Error('Cannot delete built-in agents')
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const filePath = getAgentFilePath(agent)
|
|
455
|
-
unlinkSync(filePath)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Open file in system editor - 安全版本,防止命令注入
|
|
459
|
-
async function openInEditor(filePath: string): Promise<void> {
|
|
460
|
-
// 安全验证:确保路径在允许的目录内
|
|
461
|
-
const resolvedPath = path.resolve(filePath)
|
|
462
|
-
const projectDir = process.cwd()
|
|
463
|
-
const homeDir = os.homedir()
|
|
464
|
-
|
|
465
|
-
const isSub = (base: string, target: string) => {
|
|
466
|
-
const path = require('path')
|
|
467
|
-
const rel = path.relative(path.resolve(base), path.resolve(target))
|
|
468
|
-
if (!rel || rel === '') return true
|
|
469
|
-
if (rel.startsWith('..')) return false
|
|
470
|
-
if (path.isAbsolute(rel)) return false
|
|
471
|
-
return true
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (!isSub(projectDir, resolvedPath) && !isSub(homeDir, resolvedPath)) {
|
|
475
|
-
throw new Error('Access denied: File path outside allowed directories')
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 验证文件扩展名
|
|
479
|
-
if (!resolvedPath.endsWith('.md')) {
|
|
480
|
-
throw new Error('Invalid file type: Only .md files are allowed')
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return new Promise((resolve, reject) => {
|
|
484
|
-
const platform = process.platform
|
|
485
|
-
let command: string
|
|
486
|
-
let args: string[]
|
|
487
|
-
|
|
488
|
-
// 使用spawn而不是exec,避免shell注入
|
|
489
|
-
switch (platform) {
|
|
490
|
-
case 'darwin': // macOS
|
|
491
|
-
command = 'open'
|
|
492
|
-
args = [resolvedPath]
|
|
493
|
-
break
|
|
494
|
-
case 'win32': // Windows
|
|
495
|
-
command = 'cmd'
|
|
496
|
-
args = ['/c', 'start', '', resolvedPath]
|
|
497
|
-
break
|
|
498
|
-
default: // Linux and others
|
|
499
|
-
command = 'xdg-open'
|
|
500
|
-
args = [resolvedPath]
|
|
501
|
-
break
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// 使用spawn替代exec,避免shell解释
|
|
505
|
-
const child = spawn(command, args, {
|
|
506
|
-
detached: true,
|
|
507
|
-
stdio: 'ignore',
|
|
508
|
-
// 确保没有shell解释
|
|
509
|
-
shell: false
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
child.unref() // 允许父进程退出
|
|
513
|
-
|
|
514
|
-
child.on('error', (error) => {
|
|
515
|
-
reject(new Error(`Failed to open editor: ${error.message}`))
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
child.on('exit', (code) => {
|
|
519
|
-
if (code === 0) {
|
|
520
|
-
resolve()
|
|
521
|
-
} else {
|
|
522
|
-
reject(new Error(`Editor exited with code ${code}`))
|
|
523
|
-
}
|
|
524
|
-
})
|
|
525
|
-
})
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Update existing agent
|
|
529
|
-
async function updateAgent(
|
|
530
|
-
agent: AgentConfig,
|
|
531
|
-
description: string,
|
|
532
|
-
tools: string[] | '*',
|
|
533
|
-
systemPrompt: string,
|
|
534
|
-
color?: string,
|
|
535
|
-
model?: string
|
|
536
|
-
): Promise<void> {
|
|
537
|
-
if (agent.location === 'built-in') {
|
|
538
|
-
throw new Error('Cannot update built-in agents')
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const toolsForFile = tools.length === 1 && tools[0] === '*' ? '*' : tools
|
|
542
|
-
const content = generateAgentFileContent(agent.agentType, description, toolsForFile, systemPrompt, model, color)
|
|
543
|
-
const filePath = getAgentFilePath(agent)
|
|
544
|
-
|
|
545
|
-
writeFileSync(filePath, content, { encoding: 'utf-8', flag: 'w' })
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Enhanced UI Components with Claude Code alignment
|
|
549
|
-
|
|
550
|
-
interface HeaderProps {
|
|
551
|
-
title: string
|
|
552
|
-
subtitle?: string
|
|
553
|
-
step?: number
|
|
554
|
-
totalSteps?: number
|
|
555
|
-
children?: React.ReactNode
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function Header({ title, subtitle, step, totalSteps, children }: HeaderProps) {
|
|
559
|
-
const theme = getTheme()
|
|
560
|
-
return (
|
|
561
|
-
<Box flexDirection="column">
|
|
562
|
-
<Text bold color={theme.primary}>{title}</Text>
|
|
563
|
-
{subtitle && (
|
|
564
|
-
<Text color={theme.secondary}>
|
|
565
|
-
{step && totalSteps ? `Step ${step}/${totalSteps}: ` : ''}{subtitle}
|
|
566
|
-
</Text>
|
|
567
|
-
)}
|
|
568
|
-
{children}
|
|
569
|
-
</Box>
|
|
570
|
-
)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
interface InstructionBarProps {
|
|
574
|
-
instructions?: string
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function InstructionBar({ instructions = "Press ↑↓ to navigate · Enter to select · Esc to go back" }: InstructionBarProps) {
|
|
578
|
-
const theme = getTheme()
|
|
579
|
-
return (
|
|
580
|
-
<Box marginTop={2}>
|
|
581
|
-
<Box borderStyle="round" borderColor={theme.secondary} paddingX={1}>
|
|
582
|
-
<Text color={theme.secondary}>{instructions}</Text>
|
|
583
|
-
</Box>
|
|
584
|
-
</Box>
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
interface SelectListProps {
|
|
589
|
-
options: Array<{ label: string; value: string }>
|
|
590
|
-
selectedIndex: number
|
|
591
|
-
onChange: (value: string) => void
|
|
592
|
-
onCancel?: () => void
|
|
593
|
-
numbered?: boolean
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function SelectList({ options, selectedIndex, onChange, onCancel, numbered = true }: SelectListProps) {
|
|
597
|
-
const theme = getTheme()
|
|
598
|
-
|
|
599
|
-
useInput((input, key) => {
|
|
600
|
-
if (key.escape && onCancel) {
|
|
601
|
-
onCancel()
|
|
602
|
-
} else if (key.return) {
|
|
603
|
-
onChange(options[selectedIndex].value)
|
|
604
|
-
}
|
|
605
|
-
})
|
|
606
|
-
|
|
607
|
-
return (
|
|
608
|
-
<Box flexDirection="column">
|
|
609
|
-
{options.map((option, idx) => (
|
|
610
|
-
<Box key={option.value}>
|
|
611
|
-
<Text color={idx === selectedIndex ? theme.primary : undefined}>
|
|
612
|
-
{idx === selectedIndex ? `${UI_ICONS.pointer} ` : " "}
|
|
613
|
-
{numbered ? `${idx + 1}. ` : ''}{option.label}
|
|
614
|
-
</Text>
|
|
615
|
-
</Box>
|
|
616
|
-
))}
|
|
617
|
-
</Box>
|
|
618
|
-
)
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
// Multiline text input component with better UX
|
|
623
|
-
interface MultilineTextInputProps {
|
|
624
|
-
value: string
|
|
625
|
-
onChange: (value: string) => void
|
|
626
|
-
placeholder?: string
|
|
627
|
-
onSubmit?: () => void
|
|
628
|
-
focus?: boolean
|
|
629
|
-
rows?: number
|
|
630
|
-
error?: string | null
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function MultilineTextInput({
|
|
634
|
-
value,
|
|
635
|
-
onChange,
|
|
636
|
-
placeholder = '',
|
|
637
|
-
onSubmit,
|
|
638
|
-
focus = true,
|
|
639
|
-
rows = 5,
|
|
640
|
-
error
|
|
641
|
-
}: MultilineTextInputProps) {
|
|
642
|
-
const theme = getTheme()
|
|
643
|
-
const [internalValue, setInternalValue] = useState(value)
|
|
644
|
-
const [cursorBlink, setCursorBlink] = useState(true)
|
|
645
|
-
|
|
646
|
-
// Sync with external value changes
|
|
647
|
-
useEffect(() => {
|
|
648
|
-
setInternalValue(value)
|
|
649
|
-
}, [value])
|
|
650
|
-
|
|
651
|
-
// Cursor blink animation
|
|
652
|
-
useEffect(() => {
|
|
653
|
-
if (!focus) return
|
|
654
|
-
const timer = setInterval(() => {
|
|
655
|
-
setCursorBlink(prev => !prev)
|
|
656
|
-
}, 500)
|
|
657
|
-
return () => clearInterval(timer)
|
|
658
|
-
}, [focus])
|
|
659
|
-
|
|
660
|
-
// Calculate display metrics
|
|
661
|
-
const lines = internalValue.split('\n')
|
|
662
|
-
const lineCount = lines.length
|
|
663
|
-
const charCount = internalValue.length
|
|
664
|
-
const isEmpty = !internalValue.trim()
|
|
665
|
-
const hasContent = !isEmpty
|
|
666
|
-
|
|
667
|
-
// Format lines for display with word wrapping
|
|
668
|
-
const formatLines = (text: string): string[] => {
|
|
669
|
-
if (!text && placeholder) {
|
|
670
|
-
return [placeholder]
|
|
671
|
-
}
|
|
672
|
-
const maxWidth = 70 // Maximum characters per line
|
|
673
|
-
const result: string[] = []
|
|
674
|
-
const textLines = text.split('\n')
|
|
675
|
-
|
|
676
|
-
textLines.forEach(line => {
|
|
677
|
-
if (line.length <= maxWidth) {
|
|
678
|
-
result.push(line)
|
|
679
|
-
} else {
|
|
680
|
-
// Word wrap long lines
|
|
681
|
-
let remaining = line
|
|
682
|
-
while (remaining.length > 0) {
|
|
683
|
-
result.push(remaining.slice(0, maxWidth))
|
|
684
|
-
remaining = remaining.slice(maxWidth)
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
})
|
|
688
|
-
|
|
689
|
-
return result.length > 0 ? result : ['']
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const displayLines = formatLines(internalValue)
|
|
693
|
-
const visibleLines = displayLines.slice(Math.max(0, displayLines.length - rows))
|
|
694
|
-
|
|
695
|
-
// Handle submit
|
|
696
|
-
const handleSubmit = () => {
|
|
697
|
-
if (internalValue.trim() && onSubmit) {
|
|
698
|
-
onSubmit()
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
return (
|
|
703
|
-
<Box flexDirection="column" width="100%">
|
|
704
|
-
{/* Modern card-style input container */}
|
|
705
|
-
<Box flexDirection="column">
|
|
706
|
-
{/* Input area */}
|
|
707
|
-
<Box
|
|
708
|
-
borderStyle="round"
|
|
709
|
-
borderColor={focus ? theme.primary : 'gray'}
|
|
710
|
-
paddingX={2}
|
|
711
|
-
paddingY={1}
|
|
712
|
-
minHeight={rows + 2}
|
|
713
|
-
>
|
|
714
|
-
<Box flexDirection="column">
|
|
715
|
-
{/* Use ink-text-input for better input handling */}
|
|
716
|
-
<InkTextInput
|
|
717
|
-
value={internalValue}
|
|
718
|
-
onChange={(val) => {
|
|
719
|
-
setInternalValue(val)
|
|
720
|
-
onChange(val)
|
|
721
|
-
}}
|
|
722
|
-
onSubmit={handleSubmit}
|
|
723
|
-
focus={focus}
|
|
724
|
-
placeholder={placeholder}
|
|
725
|
-
/>
|
|
726
|
-
|
|
727
|
-
{/* Show cursor indicator when focused */}
|
|
728
|
-
{focus && cursorBlink && hasContent && (
|
|
729
|
-
<Text color={theme.primary}>_</Text>
|
|
730
|
-
)}
|
|
731
|
-
</Box>
|
|
732
|
-
</Box>
|
|
733
|
-
|
|
734
|
-
{/* Status bar */}
|
|
735
|
-
<Box marginTop={1} flexDirection="row" justifyContent="space-between">
|
|
736
|
-
<Box>
|
|
737
|
-
{hasContent ? (
|
|
738
|
-
<Text color={theme.success}>
|
|
739
|
-
✓ {charCount} chars • {lineCount} line{lineCount !== 1 ? 's' : ''}
|
|
740
|
-
</Text>
|
|
741
|
-
) : (
|
|
742
|
-
<Text dimColor>○ Type to begin...</Text>
|
|
743
|
-
)}
|
|
744
|
-
</Box>
|
|
745
|
-
<Box>
|
|
746
|
-
{error ? (
|
|
747
|
-
<Text color={theme.error}>⚠ {error}</Text>
|
|
748
|
-
) : (
|
|
749
|
-
<Text dimColor>
|
|
750
|
-
{hasContent ? 'Ready' : 'Waiting'}
|
|
751
|
-
</Text>
|
|
752
|
-
)}
|
|
753
|
-
</Box>
|
|
754
|
-
</Box>
|
|
755
|
-
</Box>
|
|
756
|
-
|
|
757
|
-
{/* Instructions */}
|
|
758
|
-
<Box marginTop={1}>
|
|
759
|
-
<Text dimColor>
|
|
760
|
-
Press Enter to submit · Shift+Enter for new line
|
|
761
|
-
</Text>
|
|
762
|
-
</Box>
|
|
763
|
-
</Box>
|
|
764
|
-
)
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Loading spinner component
|
|
768
|
-
interface LoadingSpinnerProps {
|
|
769
|
-
text?: string
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function LoadingSpinner({ text }: LoadingSpinnerProps) {
|
|
773
|
-
const theme = getTheme()
|
|
774
|
-
const [frame, setFrame] = useState(0)
|
|
775
|
-
|
|
776
|
-
useEffect(() => {
|
|
777
|
-
const interval = setInterval(() => {
|
|
778
|
-
setFrame(prev => (prev + 1) % UI_ICONS.loading.length)
|
|
779
|
-
}, 100)
|
|
780
|
-
return () => clearInterval(interval)
|
|
781
|
-
}, [])
|
|
782
|
-
|
|
783
|
-
return (
|
|
784
|
-
<Box>
|
|
785
|
-
<Text color={theme.primary}>{UI_ICONS.loading[frame]}</Text>
|
|
786
|
-
{text && <Text color={theme.secondary}> {text}</Text>}
|
|
787
|
-
</Box>
|
|
788
|
-
)
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Complete agents UI with comprehensive state management
|
|
792
|
-
interface AgentsUIProps {
|
|
793
|
-
onExit: (message?: string) => void
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function AgentsUI({ onExit }: AgentsUIProps) {
|
|
797
|
-
const theme = getTheme()
|
|
798
|
-
|
|
799
|
-
// Core state management
|
|
800
|
-
const [modeState, setModeState] = useState<ModeState>({
|
|
801
|
-
mode: "list-agents",
|
|
802
|
-
location: "all" as AgentLocation
|
|
803
|
-
})
|
|
804
|
-
|
|
805
|
-
const [agents, setAgents] = useState<AgentConfig[]>([])
|
|
806
|
-
const [changes, setChanges] = useState<string[]>([])
|
|
807
|
-
const [refreshKey, setRefreshKey] = useState(0)
|
|
808
|
-
const [loading, setLoading] = useState(true)
|
|
809
|
-
const [tools, setTools] = useState<Tool[]>([])
|
|
810
|
-
|
|
811
|
-
// Creation state using reducer for complex flow management
|
|
812
|
-
const [createState, setCreateState] = useReducer(
|
|
813
|
-
(state: CreateState, action: any) => {
|
|
814
|
-
switch (action.type) {
|
|
815
|
-
case 'RESET':
|
|
816
|
-
return {
|
|
817
|
-
location: null,
|
|
818
|
-
agentType: '',
|
|
819
|
-
method: null,
|
|
820
|
-
generationPrompt: '',
|
|
821
|
-
whenToUse: '',
|
|
822
|
-
selectedTools: [],
|
|
823
|
-
selectedModel: null,
|
|
824
|
-
selectedColor: null,
|
|
825
|
-
systemPrompt: '',
|
|
826
|
-
isGenerating: false,
|
|
827
|
-
wasGenerated: false,
|
|
828
|
-
isAIGenerated: false,
|
|
829
|
-
error: null,
|
|
830
|
-
warnings: [],
|
|
831
|
-
agentTypeCursor: 0,
|
|
832
|
-
whenToUseCursor: 0,
|
|
833
|
-
promptCursor: 0,
|
|
834
|
-
generationPromptCursor: 0
|
|
835
|
-
}
|
|
836
|
-
case 'SET_LOCATION':
|
|
837
|
-
return { ...state, location: action.value }
|
|
838
|
-
case 'SET_METHOD':
|
|
839
|
-
return { ...state, method: action.value }
|
|
840
|
-
case 'SET_AGENT_TYPE':
|
|
841
|
-
return { ...state, agentType: action.value, error: null }
|
|
842
|
-
case 'SET_GENERATION_PROMPT':
|
|
843
|
-
return { ...state, generationPrompt: action.value }
|
|
844
|
-
case 'SET_WHEN_TO_USE':
|
|
845
|
-
return { ...state, whenToUse: action.value, error: null }
|
|
846
|
-
case 'SET_SELECTED_TOOLS':
|
|
847
|
-
return { ...state, selectedTools: action.value }
|
|
848
|
-
case 'SET_SELECTED_MODEL':
|
|
849
|
-
return { ...state, selectedModel: action.value }
|
|
850
|
-
case 'SET_SELECTED_COLOR':
|
|
851
|
-
return { ...state, selectedColor: action.value }
|
|
852
|
-
case 'SET_SYSTEM_PROMPT':
|
|
853
|
-
return { ...state, systemPrompt: action.value }
|
|
854
|
-
case 'SET_IS_GENERATING':
|
|
855
|
-
return { ...state, isGenerating: action.value }
|
|
856
|
-
case 'SET_WAS_GENERATED':
|
|
857
|
-
return { ...state, wasGenerated: action.value }
|
|
858
|
-
case 'SET_IS_AI_GENERATED':
|
|
859
|
-
return { ...state, isAIGenerated: action.value }
|
|
860
|
-
case 'SET_ERROR':
|
|
861
|
-
return { ...state, error: action.value }
|
|
862
|
-
case 'SET_WARNINGS':
|
|
863
|
-
return { ...state, warnings: action.value }
|
|
864
|
-
case 'SET_CURSOR':
|
|
865
|
-
return { ...state, [action.field]: action.value }
|
|
866
|
-
default:
|
|
867
|
-
return state
|
|
868
|
-
}
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
location: null,
|
|
872
|
-
agentType: '',
|
|
873
|
-
method: null,
|
|
874
|
-
generationPrompt: '',
|
|
875
|
-
whenToUse: '',
|
|
876
|
-
selectedTools: [],
|
|
877
|
-
selectedModel: null,
|
|
878
|
-
selectedColor: null,
|
|
879
|
-
systemPrompt: '',
|
|
880
|
-
isGenerating: false,
|
|
881
|
-
wasGenerated: false,
|
|
882
|
-
isAIGenerated: false,
|
|
883
|
-
error: null,
|
|
884
|
-
warnings: [],
|
|
885
|
-
agentTypeCursor: 0,
|
|
886
|
-
whenToUseCursor: 0,
|
|
887
|
-
promptCursor: 0,
|
|
888
|
-
generationPromptCursor: 0
|
|
889
|
-
}
|
|
890
|
-
)
|
|
891
|
-
|
|
892
|
-
// Load agents and tools dynamically
|
|
893
|
-
const loadAgents = useCallback(async () => {
|
|
894
|
-
setLoading(true)
|
|
895
|
-
clearAgentCache()
|
|
896
|
-
|
|
897
|
-
// 创建取消令牌以防止竞态条件
|
|
898
|
-
const abortController = new AbortController()
|
|
899
|
-
const loadingId = Date.now() // 用于标识这次加载
|
|
900
|
-
|
|
901
|
-
try {
|
|
902
|
-
const result = await getActiveAgents()
|
|
903
|
-
|
|
904
|
-
// 检查是否仍然是当前的加载请求
|
|
905
|
-
if (abortController.signal.aborted) {
|
|
906
|
-
return // 组件已卸载或新的加载已开始
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
setAgents(result)
|
|
910
|
-
|
|
911
|
-
// Update selectedAgent if there's one currently selected (for live reload)
|
|
912
|
-
if (modeState.selectedAgent) {
|
|
913
|
-
const freshSelectedAgent = result.find(a => a.agentType === modeState.selectedAgent!.agentType)
|
|
914
|
-
if (freshSelectedAgent) {
|
|
915
|
-
setModeState(prev => ({ ...prev, selectedAgent: freshSelectedAgent }))
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Load available tools dynamically from tool registry
|
|
920
|
-
const availableTools: Tool[] = []
|
|
921
|
-
|
|
922
|
-
// Core built-in tools
|
|
923
|
-
let coreTools = [
|
|
924
|
-
{ name: 'Read', description: 'Read files from filesystem' },
|
|
925
|
-
{ name: 'Write', description: 'Write files to filesystem' },
|
|
926
|
-
{ name: 'Edit', description: 'Edit existing files' },
|
|
927
|
-
{ name: 'MultiEdit', description: 'Make multiple edits to files' },
|
|
928
|
-
{ name: 'NotebookEdit', description: 'Edit Jupyter notebooks' },
|
|
929
|
-
{ name: 'Bash', description: 'Execute bash commands' },
|
|
930
|
-
{ name: 'Glob', description: 'Find files matching patterns' },
|
|
931
|
-
{ name: 'Grep', description: 'Search file contents' },
|
|
932
|
-
{ name: 'LS', description: 'List directory contents' },
|
|
933
|
-
{ name: 'WebFetch', description: 'Fetch web content' },
|
|
934
|
-
{ name: 'WebSearch', description: 'Search the web' },
|
|
935
|
-
{ name: 'TodoWrite', description: 'Manage task lists' }
|
|
936
|
-
]
|
|
937
|
-
// Hide agent orchestration/self-control tools for subagent configs
|
|
938
|
-
coreTools = coreTools.filter(t => t.name !== 'Task' && t.name !== 'ExitPlanMode')
|
|
939
|
-
|
|
940
|
-
availableTools.push(...coreTools)
|
|
941
|
-
|
|
942
|
-
// Try to load MCP tools dynamically
|
|
943
|
-
try {
|
|
944
|
-
const mcpTools = await getMCPTools()
|
|
945
|
-
if (Array.isArray(mcpTools) && mcpTools.length > 0) {
|
|
946
|
-
availableTools.push(...mcpTools)
|
|
947
|
-
}
|
|
948
|
-
} catch (error) {
|
|
949
|
-
console.warn('Failed to load MCP tools:', error)
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (!abortController.signal.aborted) {
|
|
953
|
-
setTools(availableTools)
|
|
954
|
-
}
|
|
955
|
-
} catch (error) {
|
|
956
|
-
if (!abortController.signal.aborted) {
|
|
957
|
-
console.error('Failed to load agents:', error)
|
|
958
|
-
}
|
|
959
|
-
} finally {
|
|
960
|
-
if (!abortController.signal.aborted) {
|
|
961
|
-
setLoading(false)
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// 返回取消函数供useEffect使用
|
|
966
|
-
return () => abortController.abort()
|
|
967
|
-
}, [])
|
|
968
|
-
|
|
969
|
-
// Remove mock MCP loader; real MCP tools are loaded via getMCPTools()
|
|
970
|
-
|
|
971
|
-
useEffect(() => {
|
|
972
|
-
let cleanup: (() => void) | undefined
|
|
973
|
-
|
|
974
|
-
const load = async () => {
|
|
975
|
-
cleanup = await loadAgents()
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
load()
|
|
979
|
-
|
|
980
|
-
return () => {
|
|
981
|
-
if (cleanup) {
|
|
982
|
-
cleanup()
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}, [refreshKey, loadAgents])
|
|
986
|
-
|
|
987
|
-
// Local file watcher removed; rely on global watcher started in CLI.
|
|
988
|
-
|
|
989
|
-
// Global keyboard handling: ESC 逐级返回
|
|
990
|
-
useInput((input, key) => {
|
|
991
|
-
if (!key.escape) return
|
|
992
|
-
|
|
993
|
-
const changesSummary = changes.length > 0 ?
|
|
994
|
-
`Agent changes:\n${changes.join('\n')}` : undefined
|
|
995
|
-
|
|
996
|
-
const current = modeState.mode
|
|
997
|
-
|
|
998
|
-
if (current === 'list-agents') {
|
|
999
|
-
onExit(changesSummary)
|
|
1000
|
-
return
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// Hierarchical back navigation
|
|
1004
|
-
switch (current) {
|
|
1005
|
-
case 'create-location':
|
|
1006
|
-
setModeState({ mode: 'list-agents', location: 'all' as AgentLocation })
|
|
1007
|
-
break
|
|
1008
|
-
case 'create-method':
|
|
1009
|
-
setModeState({ mode: 'create-location', location: modeState.location })
|
|
1010
|
-
break
|
|
1011
|
-
case 'create-generate':
|
|
1012
|
-
setModeState({ mode: 'create-location', location: modeState.location })
|
|
1013
|
-
break
|
|
1014
|
-
case 'create-type':
|
|
1015
|
-
setModeState({ mode: 'create-generate', location: modeState.location })
|
|
1016
|
-
break
|
|
1017
|
-
case 'create-prompt':
|
|
1018
|
-
setModeState({ mode: 'create-type', location: modeState.location })
|
|
1019
|
-
break
|
|
1020
|
-
case 'create-description':
|
|
1021
|
-
setModeState({ mode: 'create-prompt', location: modeState.location })
|
|
1022
|
-
break
|
|
1023
|
-
case 'create-tools':
|
|
1024
|
-
setModeState({ mode: 'create-description', location: modeState.location })
|
|
1025
|
-
break
|
|
1026
|
-
case 'create-model':
|
|
1027
|
-
setModeState({ mode: 'create-tools', location: modeState.location })
|
|
1028
|
-
break
|
|
1029
|
-
case 'create-color':
|
|
1030
|
-
setModeState({ mode: 'create-model', location: modeState.location })
|
|
1031
|
-
break
|
|
1032
|
-
case 'create-confirm':
|
|
1033
|
-
setModeState({ mode: 'create-color', location: modeState.location })
|
|
1034
|
-
break
|
|
1035
|
-
case 'agent-menu':
|
|
1036
|
-
setModeState({ mode: 'list-agents', location: 'all' as AgentLocation })
|
|
1037
|
-
break
|
|
1038
|
-
case 'view-agent':
|
|
1039
|
-
setModeState({ mode: 'agent-menu', selectedAgent: modeState.selectedAgent })
|
|
1040
|
-
break
|
|
1041
|
-
case 'edit-agent':
|
|
1042
|
-
setModeState({ mode: 'agent-menu', selectedAgent: modeState.selectedAgent })
|
|
1043
|
-
break
|
|
1044
|
-
case 'edit-tools':
|
|
1045
|
-
case 'edit-model':
|
|
1046
|
-
case 'edit-color':
|
|
1047
|
-
setModeState({ mode: 'edit-agent', selectedAgent: modeState.selectedAgent })
|
|
1048
|
-
break
|
|
1049
|
-
case 'delete-confirm':
|
|
1050
|
-
setModeState({ mode: 'agent-menu', selectedAgent: modeState.selectedAgent })
|
|
1051
|
-
break
|
|
1052
|
-
default:
|
|
1053
|
-
setModeState({ mode: 'list-agents', location: 'all' as AgentLocation })
|
|
1054
|
-
break
|
|
1055
|
-
}
|
|
1056
|
-
})
|
|
1057
|
-
|
|
1058
|
-
// Event handlers
|
|
1059
|
-
const handleAgentSelect = useCallback((agent: AgentConfig) => {
|
|
1060
|
-
setModeState({
|
|
1061
|
-
mode: "agent-menu",
|
|
1062
|
-
location: modeState.location,
|
|
1063
|
-
selectedAgent: agent
|
|
1064
|
-
})
|
|
1065
|
-
}, [modeState])
|
|
1066
|
-
|
|
1067
|
-
const handleCreateNew = useCallback(() => {
|
|
1068
|
-
console.log('=== STARTING AGENT CREATION FLOW ===')
|
|
1069
|
-
console.log('Current mode state:', modeState)
|
|
1070
|
-
setCreateState({ type: 'RESET' })
|
|
1071
|
-
console.log('Reset create state')
|
|
1072
|
-
setModeState({ mode: "create-location" })
|
|
1073
|
-
console.log('Set mode to create-location')
|
|
1074
|
-
console.log('=== CREATE NEW HANDLER COMPLETED ===')
|
|
1075
|
-
}, [modeState])
|
|
1076
|
-
|
|
1077
|
-
const handleAgentCreated = useCallback((message: string) => {
|
|
1078
|
-
setChanges(prev => [...prev, message])
|
|
1079
|
-
setRefreshKey(prev => prev + 1)
|
|
1080
|
-
setModeState({ mode: "list-agents", location: "all" as AgentLocation })
|
|
1081
|
-
}, [])
|
|
1082
|
-
|
|
1083
|
-
const handleAgentDeleted = useCallback((message: string) => {
|
|
1084
|
-
setChanges(prev => [...prev, message])
|
|
1085
|
-
setRefreshKey(prev => prev + 1)
|
|
1086
|
-
setModeState({ mode: "list-agents", location: "all" as AgentLocation })
|
|
1087
|
-
}, [])
|
|
1088
|
-
|
|
1089
|
-
if (loading) {
|
|
1090
|
-
return (
|
|
1091
|
-
<Box flexDirection="column">
|
|
1092
|
-
<Header title="Agents">
|
|
1093
|
-
<Box marginTop={1}>
|
|
1094
|
-
<LoadingSpinner text="Loading agents..." />
|
|
1095
|
-
</Box>
|
|
1096
|
-
</Header>
|
|
1097
|
-
<InstructionBar />
|
|
1098
|
-
</Box>
|
|
1099
|
-
)
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Render based on current mode
|
|
1103
|
-
switch (modeState.mode) {
|
|
1104
|
-
case "list-agents":
|
|
1105
|
-
return (
|
|
1106
|
-
<AgentListView
|
|
1107
|
-
location={modeState.location || "all"}
|
|
1108
|
-
agents={agents}
|
|
1109
|
-
allAgents={agents}
|
|
1110
|
-
onBack={() => onExit()}
|
|
1111
|
-
onSelect={handleAgentSelect}
|
|
1112
|
-
onCreateNew={handleCreateNew}
|
|
1113
|
-
changes={changes}
|
|
1114
|
-
/>
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
case "create-location":
|
|
1118
|
-
return (
|
|
1119
|
-
<LocationSelect
|
|
1120
|
-
createState={createState}
|
|
1121
|
-
setCreateState={setCreateState}
|
|
1122
|
-
setModeState={setModeState}
|
|
1123
|
-
/>
|
|
1124
|
-
)
|
|
1125
|
-
|
|
1126
|
-
case "create-method":
|
|
1127
|
-
return (
|
|
1128
|
-
<MethodSelect
|
|
1129
|
-
createState={createState}
|
|
1130
|
-
setCreateState={setCreateState}
|
|
1131
|
-
setModeState={setModeState}
|
|
1132
|
-
/>
|
|
1133
|
-
)
|
|
1134
|
-
|
|
1135
|
-
case "create-generate":
|
|
1136
|
-
return (
|
|
1137
|
-
<GenerateStep
|
|
1138
|
-
createState={createState}
|
|
1139
|
-
setCreateState={setCreateState}
|
|
1140
|
-
setModeState={setModeState}
|
|
1141
|
-
existingAgents={agents}
|
|
1142
|
-
/>
|
|
1143
|
-
)
|
|
1144
|
-
|
|
1145
|
-
case "create-type":
|
|
1146
|
-
return (
|
|
1147
|
-
<TypeStep
|
|
1148
|
-
createState={createState}
|
|
1149
|
-
setCreateState={setCreateState}
|
|
1150
|
-
setModeState={setModeState}
|
|
1151
|
-
existingAgents={agents}
|
|
1152
|
-
/>
|
|
1153
|
-
)
|
|
1154
|
-
|
|
1155
|
-
case "create-description":
|
|
1156
|
-
return (
|
|
1157
|
-
<DescriptionStep
|
|
1158
|
-
createState={createState}
|
|
1159
|
-
setCreateState={setCreateState}
|
|
1160
|
-
setModeState={setModeState}
|
|
1161
|
-
/>
|
|
1162
|
-
)
|
|
1163
|
-
|
|
1164
|
-
case "create-tools":
|
|
1165
|
-
return (
|
|
1166
|
-
<ToolsStep
|
|
1167
|
-
createState={createState}
|
|
1168
|
-
setCreateState={setCreateState}
|
|
1169
|
-
setModeState={setModeState}
|
|
1170
|
-
tools={tools}
|
|
1171
|
-
/>
|
|
1172
|
-
)
|
|
1173
|
-
|
|
1174
|
-
case "create-model":
|
|
1175
|
-
return (
|
|
1176
|
-
<ModelStep
|
|
1177
|
-
createState={createState}
|
|
1178
|
-
setCreateState={setCreateState}
|
|
1179
|
-
setModeState={setModeState}
|
|
1180
|
-
/>
|
|
1181
|
-
)
|
|
1182
|
-
|
|
1183
|
-
case "create-color":
|
|
1184
|
-
return (
|
|
1185
|
-
<ColorStep
|
|
1186
|
-
createState={createState}
|
|
1187
|
-
setCreateState={setCreateState}
|
|
1188
|
-
setModeState={setModeState}
|
|
1189
|
-
/>
|
|
1190
|
-
)
|
|
1191
|
-
|
|
1192
|
-
case "create-prompt":
|
|
1193
|
-
return (
|
|
1194
|
-
<PromptStep
|
|
1195
|
-
createState={createState}
|
|
1196
|
-
setCreateState={setCreateState}
|
|
1197
|
-
setModeState={setModeState}
|
|
1198
|
-
/>
|
|
1199
|
-
)
|
|
1200
|
-
|
|
1201
|
-
case "create-confirm":
|
|
1202
|
-
return (
|
|
1203
|
-
<ConfirmStep
|
|
1204
|
-
createState={createState}
|
|
1205
|
-
setCreateState={setCreateState}
|
|
1206
|
-
setModeState={setModeState}
|
|
1207
|
-
tools={tools}
|
|
1208
|
-
onAgentCreated={handleAgentCreated}
|
|
1209
|
-
/>
|
|
1210
|
-
)
|
|
1211
|
-
|
|
1212
|
-
case "agent-menu":
|
|
1213
|
-
return (
|
|
1214
|
-
<AgentMenu
|
|
1215
|
-
agent={modeState.selectedAgent!}
|
|
1216
|
-
setModeState={setModeState}
|
|
1217
|
-
/>
|
|
1218
|
-
)
|
|
1219
|
-
|
|
1220
|
-
case "view-agent":
|
|
1221
|
-
return (
|
|
1222
|
-
<ViewAgent
|
|
1223
|
-
agent={modeState.selectedAgent!}
|
|
1224
|
-
tools={tools}
|
|
1225
|
-
setModeState={setModeState}
|
|
1226
|
-
/>
|
|
1227
|
-
)
|
|
1228
|
-
|
|
1229
|
-
case "edit-agent":
|
|
1230
|
-
return (
|
|
1231
|
-
<EditMenu
|
|
1232
|
-
agent={modeState.selectedAgent!}
|
|
1233
|
-
setModeState={setModeState}
|
|
1234
|
-
/>
|
|
1235
|
-
)
|
|
1236
|
-
|
|
1237
|
-
case "edit-tools":
|
|
1238
|
-
return (
|
|
1239
|
-
<EditToolsStep
|
|
1240
|
-
agent={modeState.selectedAgent!}
|
|
1241
|
-
tools={tools}
|
|
1242
|
-
setModeState={setModeState}
|
|
1243
|
-
onAgentUpdated={(message, updated) => {
|
|
1244
|
-
setChanges(prev => [...prev, message])
|
|
1245
|
-
setRefreshKey(prev => prev + 1)
|
|
1246
|
-
setModeState({ mode: "agent-menu", selectedAgent: updated })
|
|
1247
|
-
}}
|
|
1248
|
-
/>
|
|
1249
|
-
)
|
|
1250
|
-
|
|
1251
|
-
case "edit-model":
|
|
1252
|
-
return (
|
|
1253
|
-
<EditModelStep
|
|
1254
|
-
agent={modeState.selectedAgent!}
|
|
1255
|
-
setModeState={setModeState}
|
|
1256
|
-
onAgentUpdated={(message, updated) => {
|
|
1257
|
-
setChanges(prev => [...prev, message])
|
|
1258
|
-
setRefreshKey(prev => prev + 1)
|
|
1259
|
-
setModeState({ mode: "agent-menu", selectedAgent: updated })
|
|
1260
|
-
}}
|
|
1261
|
-
/>
|
|
1262
|
-
)
|
|
1263
|
-
|
|
1264
|
-
case "edit-color":
|
|
1265
|
-
return (
|
|
1266
|
-
<EditColorStep
|
|
1267
|
-
agent={modeState.selectedAgent!}
|
|
1268
|
-
setModeState={setModeState}
|
|
1269
|
-
onAgentUpdated={(message, updated) => {
|
|
1270
|
-
setChanges(prev => [...prev, message])
|
|
1271
|
-
setRefreshKey(prev => prev + 1)
|
|
1272
|
-
setModeState({ mode: "agent-menu", selectedAgent: updated })
|
|
1273
|
-
}}
|
|
1274
|
-
/>
|
|
1275
|
-
)
|
|
1276
|
-
|
|
1277
|
-
case "delete-confirm":
|
|
1278
|
-
return (
|
|
1279
|
-
<DeleteConfirm
|
|
1280
|
-
agent={modeState.selectedAgent!}
|
|
1281
|
-
setModeState={setModeState}
|
|
1282
|
-
onAgentDeleted={handleAgentDeleted}
|
|
1283
|
-
/>
|
|
1284
|
-
)
|
|
1285
|
-
|
|
1286
|
-
default:
|
|
1287
|
-
return (
|
|
1288
|
-
<Box flexDirection="column">
|
|
1289
|
-
<Header title="Agents">
|
|
1290
|
-
<Text>Mode: {modeState.mode} (Not implemented yet)</Text>
|
|
1291
|
-
<Box marginTop={1}>
|
|
1292
|
-
<Text>Press Esc to go back</Text>
|
|
1293
|
-
</Box>
|
|
1294
|
-
</Header>
|
|
1295
|
-
<InstructionBar instructions="Esc to go back" />
|
|
1296
|
-
</Box>
|
|
1297
|
-
)
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
interface AgentListProps {
|
|
1302
|
-
location: AgentLocation
|
|
1303
|
-
agents: AgentConfig[]
|
|
1304
|
-
allAgents: AgentConfig[]
|
|
1305
|
-
onBack: () => void
|
|
1306
|
-
onSelect: (agent: AgentConfig) => void
|
|
1307
|
-
onCreateNew?: () => void
|
|
1308
|
-
changes: string[]
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
function AgentListView({
|
|
1312
|
-
location,
|
|
1313
|
-
agents,
|
|
1314
|
-
allAgents,
|
|
1315
|
-
onBack,
|
|
1316
|
-
onSelect,
|
|
1317
|
-
onCreateNew,
|
|
1318
|
-
changes
|
|
1319
|
-
}: AgentListProps) {
|
|
1320
|
-
const theme = getTheme()
|
|
1321
|
-
const allAgentsList = allAgents || agents
|
|
1322
|
-
const customAgents = allAgentsList.filter(a => a.location !== "built-in")
|
|
1323
|
-
const builtInAgents = allAgentsList.filter(a => a.location === "built-in")
|
|
1324
|
-
|
|
1325
|
-
const [selectedAgent, setSelectedAgent] = useState<AgentConfig | null>(null)
|
|
1326
|
-
const [onCreateOption, setOnCreateOption] = useState(true)
|
|
1327
|
-
const [currentLocation, setCurrentLocation] = useState<AgentLocation>(location)
|
|
1328
|
-
const [inLocationTabs, setInLocationTabs] = useState(false)
|
|
1329
|
-
const [selectedLocationTab, setSelectedLocationTab] = useState(0)
|
|
1330
|
-
|
|
1331
|
-
const locationTabs = [
|
|
1332
|
-
{ label: "All", value: "all" as AgentLocation },
|
|
1333
|
-
{ label: "Personal", value: "user" as AgentLocation },
|
|
1334
|
-
{ label: "Project", value: "project" as AgentLocation }
|
|
1335
|
-
]
|
|
1336
|
-
|
|
1337
|
-
const activeMap = useMemo(() => {
|
|
1338
|
-
const map = new Map<string, AgentConfig>()
|
|
1339
|
-
agents.forEach(a => map.set(a.agentType, a))
|
|
1340
|
-
return map
|
|
1341
|
-
}, [agents])
|
|
1342
|
-
|
|
1343
|
-
const checkOverride = (agent: AgentConfig) => {
|
|
1344
|
-
const active = activeMap.get(agent.agentType)
|
|
1345
|
-
const isOverridden = !!(active && active.location !== agent.location)
|
|
1346
|
-
return {
|
|
1347
|
-
isOverridden,
|
|
1348
|
-
overriddenBy: isOverridden ? active.location : null
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
const renderCreateOption = () => (
|
|
1353
|
-
<Box flexDirection="row" gap={1}>
|
|
1354
|
-
<Text color={onCreateOption ? theme.primary : undefined}>
|
|
1355
|
-
{onCreateOption ? `${UI_ICONS.pointer} ` : " "}
|
|
1356
|
-
</Text>
|
|
1357
|
-
<Text bold color={onCreateOption ? theme.primary : undefined}>
|
|
1358
|
-
✨ Create new agent
|
|
1359
|
-
</Text>
|
|
1360
|
-
</Box>
|
|
1361
|
-
)
|
|
1362
|
-
|
|
1363
|
-
const renderAgent = (agent: AgentConfig, isBuiltIn = false) => {
|
|
1364
|
-
const isSelected = !isBuiltIn && !onCreateOption &&
|
|
1365
|
-
selectedAgent?.agentType === agent.agentType &&
|
|
1366
|
-
selectedAgent?.location === agent.location
|
|
1367
|
-
const { isOverridden, overriddenBy } = checkOverride(agent)
|
|
1368
|
-
const dimmed = isBuiltIn || isOverridden
|
|
1369
|
-
const color = !isBuiltIn && isSelected ? theme.primary : undefined
|
|
1370
|
-
|
|
1371
|
-
// Extract model from agent metadata
|
|
1372
|
-
const agentModel = (agent as any).model || null
|
|
1373
|
-
const modelDisplay = getDisplayModelName(agentModel)
|
|
1374
|
-
|
|
1375
|
-
return (
|
|
1376
|
-
<Box key={`${agent.agentType}-${agent.location}`} flexDirection="row" alignItems="center">
|
|
1377
|
-
<Box flexDirection="row" alignItems="center" minWidth={3}>
|
|
1378
|
-
<Text dimColor={dimmed && !isSelected} color={color}>
|
|
1379
|
-
{isBuiltIn ? "" : isSelected ? `${UI_ICONS.pointer} ` : " "}
|
|
1380
|
-
</Text>
|
|
1381
|
-
</Box>
|
|
1382
|
-
<Box flexDirection="row" alignItems="center" flexGrow={1}>
|
|
1383
|
-
<Text dimColor={dimmed && !isSelected} color={color}>
|
|
1384
|
-
{agent.agentType}
|
|
1385
|
-
</Text>
|
|
1386
|
-
<Text dimColor={true} color={dimmed ? undefined : 'gray'}>
|
|
1387
|
-
{" · "}{modelDisplay}
|
|
1388
|
-
</Text>
|
|
1389
|
-
</Box>
|
|
1390
|
-
{overriddenBy && (
|
|
1391
|
-
<Box marginLeft={1}>
|
|
1392
|
-
<Text dimColor={!isSelected} color={isSelected ? 'yellow' : 'gray'}>
|
|
1393
|
-
{UI_ICONS.warning} overridden by {overriddenBy}
|
|
1394
|
-
</Text>
|
|
1395
|
-
</Box>
|
|
1396
|
-
)}
|
|
1397
|
-
</Box>
|
|
1398
|
-
)
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
const displayAgents = useMemo(() => {
|
|
1402
|
-
if (currentLocation === "all") {
|
|
1403
|
-
return [
|
|
1404
|
-
...customAgents.filter(a => a.location === "user"),
|
|
1405
|
-
...customAgents.filter(a => a.location === "project")
|
|
1406
|
-
]
|
|
1407
|
-
} else if (currentLocation === "user" || currentLocation === "project") {
|
|
1408
|
-
return customAgents.filter(a => a.location === currentLocation)
|
|
1409
|
-
}
|
|
1410
|
-
return customAgents
|
|
1411
|
-
}, [customAgents, currentLocation])
|
|
1412
|
-
|
|
1413
|
-
// 更新当前选中的标签索引
|
|
1414
|
-
useEffect(() => {
|
|
1415
|
-
const tabIndex = locationTabs.findIndex(tab => tab.value === currentLocation)
|
|
1416
|
-
if (tabIndex !== -1) {
|
|
1417
|
-
setSelectedLocationTab(tabIndex)
|
|
1418
|
-
}
|
|
1419
|
-
}, [currentLocation, locationTabs])
|
|
1420
|
-
|
|
1421
|
-
// 确保当有agents时,初始化选择状态
|
|
1422
|
-
useEffect(() => {
|
|
1423
|
-
if (displayAgents.length > 0 && !selectedAgent && !onCreateOption) {
|
|
1424
|
-
setOnCreateOption(true) // 默认选择创建选项
|
|
1425
|
-
}
|
|
1426
|
-
}, [displayAgents.length, selectedAgent, onCreateOption])
|
|
1427
|
-
|
|
1428
|
-
useInput((input, key) => {
|
|
1429
|
-
if (key.escape) {
|
|
1430
|
-
if (inLocationTabs) {
|
|
1431
|
-
setInLocationTabs(false)
|
|
1432
|
-
return
|
|
1433
|
-
}
|
|
1434
|
-
onBack()
|
|
1435
|
-
return
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
if (key.return) {
|
|
1439
|
-
if (inLocationTabs) {
|
|
1440
|
-
setCurrentLocation(locationTabs[selectedLocationTab].value)
|
|
1441
|
-
setInLocationTabs(false)
|
|
1442
|
-
return
|
|
1443
|
-
}
|
|
1444
|
-
if (onCreateOption && onCreateNew) {
|
|
1445
|
-
onCreateNew()
|
|
1446
|
-
} else if (selectedAgent) {
|
|
1447
|
-
onSelect(selectedAgent)
|
|
1448
|
-
}
|
|
1449
|
-
return
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// Tab键进入/退出标签导航
|
|
1453
|
-
if (key.tab) {
|
|
1454
|
-
setInLocationTabs(!inLocationTabs)
|
|
1455
|
-
return
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
// 在标签导航模式
|
|
1459
|
-
if (inLocationTabs) {
|
|
1460
|
-
if (key.leftArrow) {
|
|
1461
|
-
setSelectedLocationTab(prev => prev > 0 ? prev - 1 : locationTabs.length - 1)
|
|
1462
|
-
} else if (key.rightArrow) {
|
|
1463
|
-
setSelectedLocationTab(prev => prev < locationTabs.length - 1 ? prev + 1 : 0)
|
|
1464
|
-
}
|
|
1465
|
-
return
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
// 键盘导航 - 这是关键缺失的功能
|
|
1469
|
-
if (key.upArrow || key.downArrow) {
|
|
1470
|
-
const allNavigableItems = []
|
|
1471
|
-
|
|
1472
|
-
// 添加创建选项
|
|
1473
|
-
if (onCreateNew) {
|
|
1474
|
-
allNavigableItems.push({ type: 'create', agent: null })
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
// 添加可导航的agents
|
|
1478
|
-
displayAgents.forEach(agent => {
|
|
1479
|
-
const { isOverridden } = checkOverride(agent)
|
|
1480
|
-
if (!isOverridden) { // 只显示未被覆盖的agents
|
|
1481
|
-
allNavigableItems.push({ type: 'agent', agent })
|
|
1482
|
-
}
|
|
1483
|
-
})
|
|
1484
|
-
|
|
1485
|
-
if (allNavigableItems.length === 0) return
|
|
1486
|
-
|
|
1487
|
-
if (key.upArrow) {
|
|
1488
|
-
if (onCreateOption) {
|
|
1489
|
-
// 从创建选项向上到最后一个agent
|
|
1490
|
-
const lastAgent = allNavigableItems[allNavigableItems.length - 1]
|
|
1491
|
-
if (lastAgent.type === 'agent') {
|
|
1492
|
-
setSelectedAgent(lastAgent.agent)
|
|
1493
|
-
setOnCreateOption(false)
|
|
1494
|
-
}
|
|
1495
|
-
} else if (selectedAgent) {
|
|
1496
|
-
const currentIndex = allNavigableItems.findIndex(
|
|
1497
|
-
item => item.type === 'agent' &&
|
|
1498
|
-
item.agent?.agentType === selectedAgent.agentType &&
|
|
1499
|
-
item.agent?.location === selectedAgent.location
|
|
1500
|
-
)
|
|
1501
|
-
if (currentIndex > 0) {
|
|
1502
|
-
const prevItem = allNavigableItems[currentIndex - 1]
|
|
1503
|
-
if (prevItem.type === 'create') {
|
|
1504
|
-
setOnCreateOption(true)
|
|
1505
|
-
setSelectedAgent(null)
|
|
1506
|
-
} else {
|
|
1507
|
-
setSelectedAgent(prevItem.agent)
|
|
1508
|
-
}
|
|
1509
|
-
} else {
|
|
1510
|
-
// 到达顶部,回到创建选项
|
|
1511
|
-
if (onCreateNew) {
|
|
1512
|
-
setOnCreateOption(true)
|
|
1513
|
-
setSelectedAgent(null)
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
} else if (key.downArrow) {
|
|
1518
|
-
if (onCreateOption) {
|
|
1519
|
-
// 从创建选项向下到第一个agent
|
|
1520
|
-
const firstAgent = allNavigableItems.find(item => item.type === 'agent')
|
|
1521
|
-
if (firstAgent) {
|
|
1522
|
-
setSelectedAgent(firstAgent.agent)
|
|
1523
|
-
setOnCreateOption(false)
|
|
1524
|
-
}
|
|
1525
|
-
} else if (selectedAgent) {
|
|
1526
|
-
const currentIndex = allNavigableItems.findIndex(
|
|
1527
|
-
item => item.type === 'agent' &&
|
|
1528
|
-
item.agent?.agentType === selectedAgent.agentType &&
|
|
1529
|
-
item.agent?.location === selectedAgent.location
|
|
1530
|
-
)
|
|
1531
|
-
if (currentIndex < allNavigableItems.length - 1) {
|
|
1532
|
-
const nextItem = allNavigableItems[currentIndex + 1]
|
|
1533
|
-
if (nextItem.type === 'agent') {
|
|
1534
|
-
setSelectedAgent(nextItem.agent)
|
|
1535
|
-
}
|
|
1536
|
-
} else {
|
|
1537
|
-
// 到达底部,回到创建选项
|
|
1538
|
-
if (onCreateNew) {
|
|
1539
|
-
setOnCreateOption(true)
|
|
1540
|
-
setSelectedAgent(null)
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
})
|
|
1547
|
-
|
|
1548
|
-
// 特殊的键盘输入处理组件用于空状态
|
|
1549
|
-
const EmptyStateInput = () => {
|
|
1550
|
-
useInput((input, key) => {
|
|
1551
|
-
if (key.escape) {
|
|
1552
|
-
onBack()
|
|
1553
|
-
return
|
|
1554
|
-
}
|
|
1555
|
-
if (key.return && onCreateNew) {
|
|
1556
|
-
onCreateNew()
|
|
1557
|
-
return
|
|
1558
|
-
}
|
|
1559
|
-
})
|
|
1560
|
-
return null
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
if (!agents.length || (currentLocation !== "built-in" && !customAgents.length)) {
|
|
1564
|
-
return (
|
|
1565
|
-
<Box flexDirection="column">
|
|
1566
|
-
<EmptyStateInput />
|
|
1567
|
-
<Header title="🤖 Agents" subtitle="">
|
|
1568
|
-
{onCreateNew && (
|
|
1569
|
-
<Box marginY={1}>
|
|
1570
|
-
{renderCreateOption()}
|
|
1571
|
-
</Box>
|
|
1572
|
-
)}
|
|
1573
|
-
<Box marginTop={1} flexDirection="column">
|
|
1574
|
-
<Box marginBottom={1}>
|
|
1575
|
-
<Text bold color={theme.primary}>💭 What are agents?</Text>
|
|
1576
|
-
</Box>
|
|
1577
|
-
<Text>Specialized AI assistants that Claude can delegate to for specific tasks.</Text>
|
|
1578
|
-
<Text>Each agent has its own context, prompt, and tools.</Text>
|
|
1579
|
-
|
|
1580
|
-
<Box marginTop={1} marginBottom={1}>
|
|
1581
|
-
<Text bold color={theme.primary}>💡 Popular agent ideas:</Text>
|
|
1582
|
-
</Box>
|
|
1583
|
-
<Box paddingLeft={2} flexDirection="column">
|
|
1584
|
-
<Text>• 🔍 Code Reviewer - Reviews PRs for best practices</Text>
|
|
1585
|
-
<Text>• 🔒 Security Auditor - Finds vulnerabilities</Text>
|
|
1586
|
-
<Text>• ⚡ Performance Optimizer - Improves code speed</Text>
|
|
1587
|
-
<Text>• 🧑💼 Tech Lead - Makes architecture decisions</Text>
|
|
1588
|
-
<Text>• 🎨 UX Expert - Improves user experience</Text>
|
|
1589
|
-
</Box>
|
|
1590
|
-
</Box>
|
|
1591
|
-
|
|
1592
|
-
{currentLocation !== "built-in" && builtInAgents.length > 0 && (
|
|
1593
|
-
<>
|
|
1594
|
-
<Box marginTop={1}><Text>{UI_ICONS.separator.repeat(40)}</Text></Box>
|
|
1595
|
-
<Box flexDirection="column" marginBottom={1} paddingLeft={2}>
|
|
1596
|
-
<Text bold color={theme.secondary}>Built-in (always available):</Text>
|
|
1597
|
-
{builtInAgents.map(a => renderAgent(a, true))}
|
|
1598
|
-
</Box>
|
|
1599
|
-
</>
|
|
1600
|
-
)}
|
|
1601
|
-
</Header>
|
|
1602
|
-
<InstructionBar instructions="Press Enter to create new agent · Esc to go back" />
|
|
1603
|
-
</Box>
|
|
1604
|
-
)
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
return (
|
|
1608
|
-
<Box flexDirection="column">
|
|
1609
|
-
<Header title="🤖 Agents" subtitle="">
|
|
1610
|
-
{changes.length > 0 && (
|
|
1611
|
-
<Box marginTop={1}>
|
|
1612
|
-
<Text dimColor>{changes[changes.length - 1]}</Text>
|
|
1613
|
-
</Box>
|
|
1614
|
-
)}
|
|
1615
|
-
|
|
1616
|
-
{/* Fancy location tabs */}
|
|
1617
|
-
<Box marginTop={1} flexDirection="column">
|
|
1618
|
-
<Box flexDirection="row" gap={2}>
|
|
1619
|
-
{locationTabs.map((tab, idx) => {
|
|
1620
|
-
const isActive = currentLocation === tab.value
|
|
1621
|
-
const isSelected = inLocationTabs && idx === selectedLocationTab
|
|
1622
|
-
return (
|
|
1623
|
-
<Box key={tab.value} flexDirection="row">
|
|
1624
|
-
<Text
|
|
1625
|
-
color={isSelected || isActive ? theme.primary : undefined}
|
|
1626
|
-
bold={isActive}
|
|
1627
|
-
dimColor={!isActive && !isSelected}
|
|
1628
|
-
>
|
|
1629
|
-
{isSelected ? '▶ ' : isActive ? '◉ ' : '○ '}
|
|
1630
|
-
{tab.label}
|
|
1631
|
-
</Text>
|
|
1632
|
-
{idx < locationTabs.length - 1 && <Text dimColor> | </Text>}
|
|
1633
|
-
</Box>
|
|
1634
|
-
)
|
|
1635
|
-
})}
|
|
1636
|
-
</Box>
|
|
1637
|
-
<Box marginTop={0}>
|
|
1638
|
-
<Text dimColor>
|
|
1639
|
-
{currentLocation === 'all' ? 'Showing all agents' :
|
|
1640
|
-
currentLocation === 'user' ? 'Personal agents (~/.claude/agents)' :
|
|
1641
|
-
'Project agents (.claude/agents)'}
|
|
1642
|
-
</Text>
|
|
1643
|
-
</Box>
|
|
1644
|
-
</Box>
|
|
1645
|
-
|
|
1646
|
-
<Box flexDirection="column" marginTop={1}>
|
|
1647
|
-
{onCreateNew && (
|
|
1648
|
-
<Box marginBottom={1}>
|
|
1649
|
-
{renderCreateOption()}
|
|
1650
|
-
</Box>
|
|
1651
|
-
)}
|
|
1652
|
-
|
|
1653
|
-
{currentLocation === "all" ? (
|
|
1654
|
-
<>
|
|
1655
|
-
{customAgents.filter(a => a.location === "user").length > 0 && (
|
|
1656
|
-
<>
|
|
1657
|
-
<Text bold color={theme.secondary}>Personal:</Text>
|
|
1658
|
-
{customAgents.filter(a => a.location === "user").map(a => renderAgent(a))}
|
|
1659
|
-
</>
|
|
1660
|
-
)}
|
|
1661
|
-
|
|
1662
|
-
{customAgents.filter(a => a.location === "project").length > 0 && (
|
|
1663
|
-
<>
|
|
1664
|
-
<Box marginTop={customAgents.filter(a => a.location === "user").length > 0 ? 1 : 0}>
|
|
1665
|
-
<Text bold color={theme.secondary}>Project:</Text>
|
|
1666
|
-
</Box>
|
|
1667
|
-
{customAgents.filter(a => a.location === "project").map(a => renderAgent(a))}
|
|
1668
|
-
</>
|
|
1669
|
-
)}
|
|
1670
|
-
|
|
1671
|
-
{builtInAgents.length > 0 && (
|
|
1672
|
-
<>
|
|
1673
|
-
<Box marginTop={customAgents.length > 0 ? 1 : 0}>
|
|
1674
|
-
<Text>{UI_ICONS.separator.repeat(40)}</Text>
|
|
1675
|
-
</Box>
|
|
1676
|
-
<Box flexDirection="column">
|
|
1677
|
-
<Text bold color={theme.secondary}>Built-in:</Text>
|
|
1678
|
-
{builtInAgents.map(a => renderAgent(a, true))}
|
|
1679
|
-
</Box>
|
|
1680
|
-
</>
|
|
1681
|
-
)}
|
|
1682
|
-
</>
|
|
1683
|
-
) : (
|
|
1684
|
-
<>
|
|
1685
|
-
{displayAgents.map(a => renderAgent(a))}
|
|
1686
|
-
{currentLocation !== "built-in" && builtInAgents.length > 0 && (
|
|
1687
|
-
<>
|
|
1688
|
-
<Box marginTop={1}><Text>{UI_ICONS.separator.repeat(40)}</Text></Box>
|
|
1689
|
-
<Box flexDirection="column">
|
|
1690
|
-
<Text bold color={theme.secondary}>Built-in:</Text>
|
|
1691
|
-
{builtInAgents.map(a => renderAgent(a, true))}
|
|
1692
|
-
</Box>
|
|
1693
|
-
</>
|
|
1694
|
-
)}
|
|
1695
|
-
</>
|
|
1696
|
-
)}
|
|
1697
|
-
</Box>
|
|
1698
|
-
</Header>
|
|
1699
|
-
<InstructionBar
|
|
1700
|
-
instructions={inLocationTabs ?
|
|
1701
|
-
"←→ Switch tabs • Enter Select • Tab Exit tabs" :
|
|
1702
|
-
"↑↓ Navigate • Tab Location • Enter Select"
|
|
1703
|
-
}
|
|
1704
|
-
/>
|
|
1705
|
-
</Box>
|
|
1706
|
-
)
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// Common interface for creation step props
|
|
1710
|
-
interface StepProps {
|
|
1711
|
-
createState: CreateState
|
|
1712
|
-
setCreateState: React.Dispatch<any>
|
|
1713
|
-
setModeState: (state: ModeState) => void
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
// Step 3: AI Generation
|
|
1717
|
-
interface GenerateStepProps extends StepProps {
|
|
1718
|
-
existingAgents: AgentConfig[]
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
function GenerateStep({ createState, setCreateState, setModeState, existingAgents }: GenerateStepProps) {
|
|
1722
|
-
const handleSubmit = async () => {
|
|
1723
|
-
if (createState.generationPrompt.trim()) {
|
|
1724
|
-
setCreateState({ type: 'SET_IS_GENERATING', value: true })
|
|
1725
|
-
setCreateState({ type: 'SET_ERROR', value: null })
|
|
1726
|
-
|
|
1727
|
-
try {
|
|
1728
|
-
const generated = await generateAgentWithClaude(createState.generationPrompt)
|
|
1729
|
-
|
|
1730
|
-
// Validate the generated identifier doesn't conflict
|
|
1731
|
-
const validation = validateAgentType(generated.identifier, existingAgents)
|
|
1732
|
-
let finalIdentifier = generated.identifier
|
|
1733
|
-
|
|
1734
|
-
if (!validation.isValid) {
|
|
1735
|
-
// Add a suffix to make it unique
|
|
1736
|
-
let counter = 1
|
|
1737
|
-
while (true) {
|
|
1738
|
-
const testId = `${generated.identifier}-${counter}`
|
|
1739
|
-
const testValidation = validateAgentType(testId, existingAgents)
|
|
1740
|
-
if (testValidation.isValid) {
|
|
1741
|
-
finalIdentifier = testId
|
|
1742
|
-
break
|
|
1743
|
-
}
|
|
1744
|
-
counter++
|
|
1745
|
-
if (counter > 10) {
|
|
1746
|
-
finalIdentifier = `custom-agent-${Date.now()}`
|
|
1747
|
-
break
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
setCreateState({ type: 'SET_AGENT_TYPE', value: finalIdentifier })
|
|
1753
|
-
setCreateState({ type: 'SET_WHEN_TO_USE', value: generated.whenToUse })
|
|
1754
|
-
setCreateState({ type: 'SET_SYSTEM_PROMPT', value: generated.systemPrompt })
|
|
1755
|
-
setCreateState({ type: 'SET_WAS_GENERATED', value: true })
|
|
1756
|
-
setCreateState({ type: 'SET_IS_GENERATING', value: false })
|
|
1757
|
-
setModeState({ mode: 'create-tools', location: createState.location })
|
|
1758
|
-
} catch (error) {
|
|
1759
|
-
console.error('Generation failed:', error)
|
|
1760
|
-
setCreateState({ type: 'SET_ERROR', value: 'Failed to generate agent. Please try again or use manual configuration.' })
|
|
1761
|
-
setCreateState({ type: 'SET_IS_GENERATING', value: false })
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
return (
|
|
1767
|
-
<Box flexDirection="column">
|
|
1768
|
-
<Header title="✨ New Agent" subtitle="What should it do?" step={2} totalSteps={8}>
|
|
1769
|
-
<Box marginTop={1}>
|
|
1770
|
-
{createState.isGenerating ? (
|
|
1771
|
-
<Box flexDirection="column">
|
|
1772
|
-
<Text dimColor>{createState.generationPrompt}</Text>
|
|
1773
|
-
<Box marginTop={1}>
|
|
1774
|
-
<LoadingSpinner text="Generating agent configuration..." />
|
|
1775
|
-
</Box>
|
|
1776
|
-
</Box>
|
|
1777
|
-
) : (
|
|
1778
|
-
<MultilineTextInput
|
|
1779
|
-
value={createState.generationPrompt}
|
|
1780
|
-
onChange={(value) => setCreateState({ type: 'SET_GENERATION_PROMPT', value })}
|
|
1781
|
-
placeholder="An expert that reviews pull requests for best practices, security issues, and suggests improvements..."
|
|
1782
|
-
onSubmit={handleSubmit}
|
|
1783
|
-
error={createState.error}
|
|
1784
|
-
rows={3}
|
|
1785
|
-
/>
|
|
1786
|
-
)}
|
|
1787
|
-
</Box>
|
|
1788
|
-
</Header>
|
|
1789
|
-
<InstructionBar />
|
|
1790
|
-
</Box>
|
|
1791
|
-
)
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// Step 4: Manual type input (for manual method)
|
|
1795
|
-
interface TypeStepProps extends StepProps {
|
|
1796
|
-
existingAgents: AgentConfig[]
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
function TypeStep({ createState, setCreateState, setModeState, existingAgents }: TypeStepProps) {
|
|
1800
|
-
const handleSubmit = () => {
|
|
1801
|
-
const validation = validateAgentType(createState.agentType, existingAgents)
|
|
1802
|
-
if (validation.isValid) {
|
|
1803
|
-
setModeState({ mode: 'create-prompt', location: createState.location })
|
|
1804
|
-
} else {
|
|
1805
|
-
setCreateState({ type: 'SET_ERROR', value: validation.errors[0] })
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
return (
|
|
1810
|
-
<Box flexDirection="column">
|
|
1811
|
-
<Header title="Create new agent" subtitle="Enter agent identifier" step={3} totalSteps={8}>
|
|
1812
|
-
<Box marginTop={1}>
|
|
1813
|
-
<InkTextInput
|
|
1814
|
-
value={createState.agentType}
|
|
1815
|
-
onChange={(value) => setCreateState({ type: 'SET_AGENT_TYPE', value })}
|
|
1816
|
-
placeholder="e.g. code-reviewer, tech-lead"
|
|
1817
|
-
onSubmit={handleSubmit}
|
|
1818
|
-
/>
|
|
1819
|
-
{createState.error && (
|
|
1820
|
-
<Box marginTop={1}>
|
|
1821
|
-
<Text color="red">⚠ {createState.error}</Text>
|
|
1822
|
-
</Box>
|
|
1823
|
-
)}
|
|
1824
|
-
</Box>
|
|
1825
|
-
</Header>
|
|
1826
|
-
<InstructionBar />
|
|
1827
|
-
</Box>
|
|
1828
|
-
)
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
// Step 5: Description input
|
|
1832
|
-
function DescriptionStep({ createState, setCreateState, setModeState }: StepProps) {
|
|
1833
|
-
const handleSubmit = () => {
|
|
1834
|
-
if (createState.whenToUse.trim()) {
|
|
1835
|
-
setModeState({ mode: 'create-tools', location: createState.location })
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
return (
|
|
1840
|
-
<Box flexDirection="column">
|
|
1841
|
-
<Header title="Create new agent" subtitle="Describe when to use this agent" step={5} totalSteps={8}>
|
|
1842
|
-
<Box marginTop={1}>
|
|
1843
|
-
<MultilineTextInput
|
|
1844
|
-
value={createState.whenToUse}
|
|
1845
|
-
onChange={(value) => setCreateState({ type: 'SET_WHEN_TO_USE', value })}
|
|
1846
|
-
placeholder="Use this agent when you need to review code for best practices, security issues..."
|
|
1847
|
-
onSubmit={handleSubmit}
|
|
1848
|
-
error={createState.error}
|
|
1849
|
-
rows={4}
|
|
1850
|
-
/>
|
|
1851
|
-
</Box>
|
|
1852
|
-
</Header>
|
|
1853
|
-
<InstructionBar />
|
|
1854
|
-
</Box>
|
|
1855
|
-
)
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// Step 6: Tools selection
|
|
1859
|
-
interface ToolsStepProps extends StepProps {
|
|
1860
|
-
tools: Tool[]
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
function ToolsStep({ createState, setCreateState, setModeState, tools }: ToolsStepProps) {
|
|
1864
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
1865
|
-
// Default to all tools selected initially
|
|
1866
|
-
const initialSelection = createState.selectedTools.length > 0 ?
|
|
1867
|
-
new Set(createState.selectedTools) :
|
|
1868
|
-
new Set(tools.map(t => t.name)) // Select all tools by default
|
|
1869
|
-
const [selectedTools, setSelectedTools] = useState<Set<string>>(initialSelection)
|
|
1870
|
-
const [showAdvanced, setShowAdvanced] = useState(false)
|
|
1871
|
-
const [selectedCategory, setSelectedCategory] = useState<keyof typeof TOOL_CATEGORIES | 'mcp' | 'all'>('all')
|
|
1872
|
-
|
|
1873
|
-
// Categorize tools
|
|
1874
|
-
const categorizedTools = useMemo(() => {
|
|
1875
|
-
const categories: Record<string, Tool[]> = {
|
|
1876
|
-
read: [],
|
|
1877
|
-
edit: [],
|
|
1878
|
-
execution: [],
|
|
1879
|
-
web: [],
|
|
1880
|
-
mcp: [],
|
|
1881
|
-
other: []
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
tools.forEach(tool => {
|
|
1885
|
-
let categorized = false
|
|
1886
|
-
|
|
1887
|
-
// Check MCP tools first
|
|
1888
|
-
if (tool.name.startsWith('mcp__')) {
|
|
1889
|
-
categories.mcp.push(tool)
|
|
1890
|
-
categorized = true
|
|
1891
|
-
} else {
|
|
1892
|
-
// Check built-in categories
|
|
1893
|
-
for (const [category, toolNames] of Object.entries(TOOL_CATEGORIES)) {
|
|
1894
|
-
if (Array.isArray(toolNames) && toolNames.includes(tool.name)) {
|
|
1895
|
-
categories[category as keyof typeof categories]?.push(tool)
|
|
1896
|
-
categorized = true
|
|
1897
|
-
break
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
if (!categorized) {
|
|
1903
|
-
categories.other.push(tool)
|
|
1904
|
-
}
|
|
1905
|
-
})
|
|
1906
|
-
|
|
1907
|
-
return categories
|
|
1908
|
-
}, [tools])
|
|
1909
|
-
|
|
1910
|
-
const displayTools = useMemo(() => {
|
|
1911
|
-
if (selectedCategory === 'all') {
|
|
1912
|
-
return tools
|
|
1913
|
-
}
|
|
1914
|
-
return categorizedTools[selectedCategory] || []
|
|
1915
|
-
}, [selectedCategory, tools, categorizedTools])
|
|
1916
|
-
|
|
1917
|
-
const allSelected = selectedTools.size === tools.length && tools.length > 0
|
|
1918
|
-
const categoryOptions = [
|
|
1919
|
-
{ id: 'all', label: `All (${tools.length})` },
|
|
1920
|
-
{ id: 'read', label: `Read (${categorizedTools.read.length})` },
|
|
1921
|
-
{ id: 'edit', label: `Edit (${categorizedTools.edit.length})` },
|
|
1922
|
-
{ id: 'execution', label: `Execution (${categorizedTools.execution.length})` },
|
|
1923
|
-
{ id: 'web', label: `Web (${categorizedTools.web.length})` },
|
|
1924
|
-
{ id: 'mcp', label: `MCP (${categorizedTools.mcp.length})` },
|
|
1925
|
-
{ id: 'other', label: `Other (${categorizedTools.other.length})` }
|
|
1926
|
-
].filter(cat => cat.id === 'all' || categorizedTools[cat.id]?.length > 0)
|
|
1927
|
-
|
|
1928
|
-
// Calculate category selections
|
|
1929
|
-
const readSelected = categorizedTools.read.every(tool => selectedTools.has(tool.name))
|
|
1930
|
-
const editSelected = categorizedTools.edit.every(tool => selectedTools.has(tool.name))
|
|
1931
|
-
const execSelected = categorizedTools.execution.every(tool => selectedTools.has(tool.name))
|
|
1932
|
-
const webSelected = categorizedTools.web.every(tool => selectedTools.has(tool.name))
|
|
1933
|
-
|
|
1934
|
-
const options: Array<{
|
|
1935
|
-
id: string
|
|
1936
|
-
label: string
|
|
1937
|
-
isContinue?: boolean
|
|
1938
|
-
isAll?: boolean
|
|
1939
|
-
isTool?: boolean
|
|
1940
|
-
isCategory?: boolean
|
|
1941
|
-
isAdvancedToggle?: boolean
|
|
1942
|
-
isSeparator?: boolean
|
|
1943
|
-
}> = [
|
|
1944
|
-
{ id: 'continue', label: 'Save', isContinue: true },
|
|
1945
|
-
{ id: 'separator1', label: '────────────────────────────────────', isSeparator: true },
|
|
1946
|
-
{ id: 'all', label: `${allSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} All tools`, isAll: true },
|
|
1947
|
-
{ id: 'read', label: `${readSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Read-only tools`, isCategory: true },
|
|
1948
|
-
{ id: 'edit', label: `${editSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Edit tools`, isCategory: true },
|
|
1949
|
-
{ id: 'execution', label: `${execSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Execution tools`, isCategory: true },
|
|
1950
|
-
{ id: 'separator2', label: '────────────────────────────────────', isSeparator: true },
|
|
1951
|
-
{ id: 'advanced', label: `[ ${showAdvanced ? 'Hide' : 'Show'} advanced options ]`, isAdvancedToggle: true },
|
|
1952
|
-
...(showAdvanced ? displayTools.map(tool => ({
|
|
1953
|
-
id: tool.name,
|
|
1954
|
-
label: `${selectedTools.has(tool.name) ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} ${tool.name}`,
|
|
1955
|
-
isTool: true
|
|
1956
|
-
})) : [])
|
|
1957
|
-
]
|
|
1958
|
-
|
|
1959
|
-
const handleSelect = () => {
|
|
1960
|
-
const option = options[selectedIndex] as any // Type assertion for union type
|
|
1961
|
-
if (!option) return
|
|
1962
|
-
if (option.isSeparator) return
|
|
1963
|
-
|
|
1964
|
-
if (option.isContinue) {
|
|
1965
|
-
const result = allSelected ? ['*'] : Array.from(selectedTools)
|
|
1966
|
-
setCreateState({ type: 'SET_SELECTED_TOOLS', value: result })
|
|
1967
|
-
setModeState({ mode: 'create-model', location: createState.location })
|
|
1968
|
-
} else if (option.isAdvancedToggle) {
|
|
1969
|
-
setShowAdvanced(!showAdvanced)
|
|
1970
|
-
} else if (option.isAll) {
|
|
1971
|
-
if (allSelected) {
|
|
1972
|
-
setSelectedTools(new Set())
|
|
1973
|
-
} else {
|
|
1974
|
-
setSelectedTools(new Set(tools.map(t => t.name)))
|
|
1975
|
-
}
|
|
1976
|
-
} else if (option.isCategory) {
|
|
1977
|
-
const categoryName = option.id as keyof typeof categorizedTools
|
|
1978
|
-
const categoryTools = categorizedTools[categoryName] || []
|
|
1979
|
-
const newSelected = new Set(selectedTools)
|
|
1980
|
-
|
|
1981
|
-
const categorySelected = categoryTools.every(tool => selectedTools.has(tool.name))
|
|
1982
|
-
if (categorySelected) {
|
|
1983
|
-
// Unselect all tools in this category
|
|
1984
|
-
categoryTools.forEach(tool => newSelected.delete(tool.name))
|
|
1985
|
-
} else {
|
|
1986
|
-
// Select all tools in this category
|
|
1987
|
-
categoryTools.forEach(tool => newSelected.add(tool.name))
|
|
1988
|
-
}
|
|
1989
|
-
setSelectedTools(newSelected)
|
|
1990
|
-
} else if (option.isTool) {
|
|
1991
|
-
const newSelected = new Set(selectedTools)
|
|
1992
|
-
if (newSelected.has(option.id)) {
|
|
1993
|
-
newSelected.delete(option.id)
|
|
1994
|
-
} else {
|
|
1995
|
-
newSelected.add(option.id)
|
|
1996
|
-
}
|
|
1997
|
-
setSelectedTools(newSelected)
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
useInput((input, key) => {
|
|
2002
|
-
if (key.return) {
|
|
2003
|
-
handleSelect()
|
|
2004
|
-
} else if (key.upArrow) {
|
|
2005
|
-
setSelectedIndex(prev => {
|
|
2006
|
-
let newIndex = prev > 0 ? prev - 1 : options.length - 1
|
|
2007
|
-
// Skip separators when going up
|
|
2008
|
-
while (options[newIndex] && (options[newIndex] as any).isSeparator) {
|
|
2009
|
-
newIndex = newIndex > 0 ? newIndex - 1 : options.length - 1
|
|
2010
|
-
}
|
|
2011
|
-
return newIndex
|
|
2012
|
-
})
|
|
2013
|
-
} else if (key.downArrow) {
|
|
2014
|
-
setSelectedIndex(prev => {
|
|
2015
|
-
let newIndex = prev < options.length - 1 ? prev + 1 : 0
|
|
2016
|
-
// Skip separators when going down
|
|
2017
|
-
while (options[newIndex] && (options[newIndex] as any).isSeparator) {
|
|
2018
|
-
newIndex = newIndex < options.length - 1 ? newIndex + 1 : 0
|
|
2019
|
-
}
|
|
2020
|
-
return newIndex
|
|
2021
|
-
})
|
|
2022
|
-
}
|
|
2023
|
-
})
|
|
2024
|
-
|
|
2025
|
-
return (
|
|
2026
|
-
<Box flexDirection="column">
|
|
2027
|
-
<Header title="🔧 Tool Permissions" subtitle="" step={3} totalSteps={5}>
|
|
2028
|
-
<Box flexDirection="column" marginTop={1}>
|
|
2029
|
-
{options.map((option, idx) => {
|
|
2030
|
-
const isSelected = idx === selectedIndex
|
|
2031
|
-
const isContinue = option.isContinue
|
|
2032
|
-
const isAdvancedToggle = option.isAdvancedToggle
|
|
2033
|
-
const isSeparator = option.isSeparator
|
|
2034
|
-
|
|
2035
|
-
return (
|
|
2036
|
-
<Box key={option.id}>
|
|
2037
|
-
<Text
|
|
2038
|
-
color={isSelected && !isSeparator ? 'cyan' : isSeparator ? 'gray' : undefined}
|
|
2039
|
-
bold={isContinue}
|
|
2040
|
-
dimColor={isSeparator}
|
|
2041
|
-
>
|
|
2042
|
-
{isSeparator ?
|
|
2043
|
-
option.label :
|
|
2044
|
-
`${isSelected ? `${UI_ICONS.pointer} ` : ' '}${isContinue || isAdvancedToggle ? `${option.label}` : option.label}`
|
|
2045
|
-
}
|
|
2046
|
-
</Text>
|
|
2047
|
-
{option.isTool && isSelected && tools.find(t => t.name === option.id)?.description && (
|
|
2048
|
-
<Box marginLeft={4}>
|
|
2049
|
-
<Text dimColor>{tools.find(t => t.name === option.id)?.description}</Text>
|
|
2050
|
-
</Box>
|
|
2051
|
-
)}
|
|
2052
|
-
</Box>
|
|
2053
|
-
)
|
|
2054
|
-
})}
|
|
2055
|
-
|
|
2056
|
-
<Box marginTop={1}>
|
|
2057
|
-
<Text dimColor>
|
|
2058
|
-
{allSelected ?
|
|
2059
|
-
'All tools selected' :
|
|
2060
|
-
`${selectedTools.size} of ${tools.length} tools selected`}
|
|
2061
|
-
</Text>
|
|
2062
|
-
{selectedCategory !== 'all' && (
|
|
2063
|
-
<Text dimColor>Filtering: {selectedCategory} tools</Text>
|
|
2064
|
-
)}
|
|
2065
|
-
</Box>
|
|
2066
|
-
</Box>
|
|
2067
|
-
</Header>
|
|
2068
|
-
<InstructionBar instructions="↑↓ Navigate • Enter Toggle • Esc Back" />
|
|
2069
|
-
</Box>
|
|
2070
|
-
)
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
// Step 6: Model selection (clean design like /models)
|
|
2074
|
-
function ModelStep({ createState, setCreateState, setModeState }: StepProps) {
|
|
2075
|
-
const theme = getTheme()
|
|
2076
|
-
const manager = getModelManager()
|
|
2077
|
-
const profiles = manager.getActiveModelProfiles()
|
|
2078
|
-
|
|
2079
|
-
// Group models by provider
|
|
2080
|
-
const groupedModels = profiles.reduce((acc: any, profile: any) => {
|
|
2081
|
-
const provider = profile.provider || 'Default'
|
|
2082
|
-
if (!acc[provider]) acc[provider] = []
|
|
2083
|
-
acc[provider].push(profile)
|
|
2084
|
-
return acc
|
|
2085
|
-
}, {})
|
|
2086
|
-
|
|
2087
|
-
// Flatten with inherit option
|
|
2088
|
-
const modelOptions = [
|
|
2089
|
-
{ id: null, name: '◈ Inherit from parent', provider: 'System', modelName: 'default' },
|
|
2090
|
-
...Object.entries(groupedModels).flatMap(([provider, models]: any) =>
|
|
2091
|
-
models.map((p: any) => ({
|
|
2092
|
-
id: p.modelName,
|
|
2093
|
-
name: p.name,
|
|
2094
|
-
provider: provider,
|
|
2095
|
-
modelName: p.modelName
|
|
2096
|
-
}))
|
|
2097
|
-
)
|
|
2098
|
-
]
|
|
2099
|
-
|
|
2100
|
-
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
2101
|
-
const idx = modelOptions.findIndex(m => m.id === createState.selectedModel)
|
|
2102
|
-
return idx >= 0 ? idx : 0
|
|
2103
|
-
})
|
|
2104
|
-
|
|
2105
|
-
const handleSelect = (modelId: string | null) => {
|
|
2106
|
-
setCreateState({ type: 'SET_SELECTED_MODEL', value: modelId })
|
|
2107
|
-
setModeState({ mode: 'create-color', location: createState.location })
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
useInput((input, key) => {
|
|
2111
|
-
if (key.return) {
|
|
2112
|
-
handleSelect(modelOptions[selectedIndex].id)
|
|
2113
|
-
} else if (key.upArrow) {
|
|
2114
|
-
setSelectedIndex(prev => (prev > 0 ? prev - 1 : modelOptions.length - 1))
|
|
2115
|
-
} else if (key.downArrow) {
|
|
2116
|
-
setSelectedIndex(prev => (prev < modelOptions.length - 1 ? prev + 1 : 0))
|
|
2117
|
-
}
|
|
2118
|
-
})
|
|
2119
|
-
|
|
2120
|
-
return (
|
|
2121
|
-
<Box flexDirection="column">
|
|
2122
|
-
<Header title="🤖 Select Model" subtitle="" step={4} totalSteps={5}>
|
|
2123
|
-
<Box marginTop={1} flexDirection="column">
|
|
2124
|
-
{modelOptions.map((model, index) => {
|
|
2125
|
-
const isSelected = index === selectedIndex
|
|
2126
|
-
const isInherit = model.id === null
|
|
2127
|
-
|
|
2128
|
-
return (
|
|
2129
|
-
<Box key={model.id || 'inherit'} marginBottom={0}>
|
|
2130
|
-
<Box flexDirection="row" gap={1}>
|
|
2131
|
-
<Text color={isSelected ? theme.primary : undefined}>
|
|
2132
|
-
{isSelected ? UI_ICONS.pointer : ' '}
|
|
2133
|
-
</Text>
|
|
2134
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
2135
|
-
<Box flexDirection="row" gap={1}>
|
|
2136
|
-
<Text
|
|
2137
|
-
bold={isInherit}
|
|
2138
|
-
color={isSelected ? theme.primary : undefined}
|
|
2139
|
-
>
|
|
2140
|
-
{model.name}
|
|
2141
|
-
</Text>
|
|
2142
|
-
{!isInherit && (
|
|
2143
|
-
<Text dimColor>
|
|
2144
|
-
{model.provider} • {model.modelName}
|
|
2145
|
-
</Text>
|
|
2146
|
-
)}
|
|
2147
|
-
</Box>
|
|
2148
|
-
</Box>
|
|
2149
|
-
</Box>
|
|
2150
|
-
</Box>
|
|
2151
|
-
)
|
|
2152
|
-
})}
|
|
2153
|
-
</Box>
|
|
2154
|
-
</Header>
|
|
2155
|
-
<InstructionBar instructions="↑↓ Navigate • Enter Select" />
|
|
2156
|
-
</Box>
|
|
2157
|
-
)
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
// Step 7: Color selection (using hex colors for display)
|
|
2161
|
-
function ColorStep({ createState, setCreateState, setModeState }: StepProps) {
|
|
2162
|
-
const theme = getTheme()
|
|
2163
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2164
|
-
|
|
2165
|
-
// Color options without red/green due to display issues
|
|
2166
|
-
const colors = [
|
|
2167
|
-
{ label: 'Default', value: null, displayColor: null },
|
|
2168
|
-
{ label: 'Yellow', value: 'yellow', displayColor: 'yellow' },
|
|
2169
|
-
{ label: 'Blue', value: 'blue', displayColor: 'blue' },
|
|
2170
|
-
{ label: 'Magenta', value: 'magenta', displayColor: 'magenta' },
|
|
2171
|
-
{ label: 'Cyan', value: 'cyan', displayColor: 'cyan' },
|
|
2172
|
-
{ label: 'Gray', value: 'gray', displayColor: 'gray' },
|
|
2173
|
-
{ label: 'White', value: 'white', displayColor: 'white' }
|
|
2174
|
-
]
|
|
2175
|
-
|
|
2176
|
-
const handleSelect = (value: string | null) => {
|
|
2177
|
-
setCreateState({ type: 'SET_SELECTED_COLOR', value: value })
|
|
2178
|
-
setModeState({ mode: 'create-confirm', location: createState.location })
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
useInput((input, key) => {
|
|
2182
|
-
if (key.return) {
|
|
2183
|
-
handleSelect(colors[selectedIndex].value)
|
|
2184
|
-
} else if (key.upArrow) {
|
|
2185
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : colors.length - 1)
|
|
2186
|
-
} else if (key.downArrow) {
|
|
2187
|
-
setSelectedIndex(prev => prev < colors.length - 1 ? prev + 1 : 0)
|
|
2188
|
-
}
|
|
2189
|
-
})
|
|
2190
|
-
|
|
2191
|
-
return (
|
|
2192
|
-
<Box flexDirection="column">
|
|
2193
|
-
<Header title="🎨 Color Theme" subtitle="" step={5} totalSteps={5}>
|
|
2194
|
-
<Box marginTop={1} flexDirection="column">
|
|
2195
|
-
<Box marginBottom={1}>
|
|
2196
|
-
<Text dimColor>Choose how your agent appears in the list:</Text>
|
|
2197
|
-
</Box>
|
|
2198
|
-
{colors.map((color, idx) => {
|
|
2199
|
-
const isSelected = idx === selectedIndex
|
|
2200
|
-
return (
|
|
2201
|
-
<Box key={idx} flexDirection="row">
|
|
2202
|
-
<Text color={isSelected ? theme.primary : undefined}>
|
|
2203
|
-
{isSelected ? '❯ ' : ' '}
|
|
2204
|
-
</Text>
|
|
2205
|
-
<Box minWidth={12}>
|
|
2206
|
-
<Text bold={isSelected} color={color.displayColor || undefined}>
|
|
2207
|
-
{color.label}
|
|
2208
|
-
</Text>
|
|
2209
|
-
</Box>
|
|
2210
|
-
</Box>
|
|
2211
|
-
)
|
|
2212
|
-
})}
|
|
2213
|
-
<Box marginTop={1} paddingLeft={2}>
|
|
2214
|
-
<Text>Preview: </Text>
|
|
2215
|
-
<Text bold color={colors[selectedIndex].displayColor || undefined}>
|
|
2216
|
-
{createState.agentType || 'your-agent'}
|
|
2217
|
-
</Text>
|
|
2218
|
-
</Box>
|
|
2219
|
-
</Box>
|
|
2220
|
-
</Header>
|
|
2221
|
-
<InstructionBar instructions="↑↓ Navigate • Enter Select" />
|
|
2222
|
-
</Box>
|
|
2223
|
-
)
|
|
2224
|
-
}
|
|
2225
|
-
|
|
2226
|
-
// Step 8: System prompt
|
|
2227
|
-
function PromptStep({ createState, setCreateState, setModeState }: StepProps) {
|
|
2228
|
-
const handleSubmit = () => {
|
|
2229
|
-
if (createState.systemPrompt.trim()) {
|
|
2230
|
-
setModeState({ mode: 'create-description', location: createState.location })
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
return (
|
|
2235
|
-
<Box flexDirection="column">
|
|
2236
|
-
<Header title="Create new agent" subtitle="System prompt" step={4} totalSteps={8}>
|
|
2237
|
-
<Box marginTop={1}>
|
|
2238
|
-
<MultilineTextInput
|
|
2239
|
-
value={createState.systemPrompt}
|
|
2240
|
-
onChange={(value) => setCreateState({ type: 'SET_SYSTEM_PROMPT', value })}
|
|
2241
|
-
placeholder="You are a helpful assistant that specializes in..."
|
|
2242
|
-
onSubmit={handleSubmit}
|
|
2243
|
-
error={createState.error}
|
|
2244
|
-
rows={5}
|
|
2245
|
-
/>
|
|
2246
|
-
</Box>
|
|
2247
|
-
</Header>
|
|
2248
|
-
<InstructionBar />
|
|
2249
|
-
</Box>
|
|
2250
|
-
)
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
// Step 9: Confirmation
|
|
2254
|
-
interface ConfirmStepProps extends StepProps {
|
|
2255
|
-
tools: Tool[]
|
|
2256
|
-
onAgentCreated: (message: string) => void
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
function ConfirmStep({ createState, setCreateState, setModeState, tools, onAgentCreated }: ConfirmStepProps) {
|
|
2260
|
-
const [isCreating, setIsCreating] = useState(false)
|
|
2261
|
-
const theme = getTheme()
|
|
2262
|
-
|
|
2263
|
-
const handleConfirm = async () => {
|
|
2264
|
-
setIsCreating(true)
|
|
2265
|
-
try {
|
|
2266
|
-
await saveAgent(
|
|
2267
|
-
createState.location!,
|
|
2268
|
-
createState.agentType,
|
|
2269
|
-
createState.whenToUse,
|
|
2270
|
-
createState.selectedTools,
|
|
2271
|
-
createState.systemPrompt,
|
|
2272
|
-
createState.selectedModel,
|
|
2273
|
-
createState.selectedColor || undefined
|
|
2274
|
-
)
|
|
2275
|
-
onAgentCreated(`Created agent: ${createState.agentType}`)
|
|
2276
|
-
} catch (error) {
|
|
2277
|
-
setCreateState({ type: 'SET_ERROR', value: (error as Error).message })
|
|
2278
|
-
setIsCreating(false)
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
const validation = validateAgentConfig(createState)
|
|
2283
|
-
const toolNames = createState.selectedTools.includes('*') ?
|
|
2284
|
-
'All tools' :
|
|
2285
|
-
createState.selectedTools.length > 0 ?
|
|
2286
|
-
createState.selectedTools.join(', ') :
|
|
2287
|
-
'No tools'
|
|
2288
|
-
|
|
2289
|
-
const handleEditInEditor = async () => {
|
|
2290
|
-
const filePath = createState.location === 'project'
|
|
2291
|
-
? path.join(process.cwd(), '.claude', 'agents', `${createState.agentType}.md`)
|
|
2292
|
-
: path.join(os.homedir(), '.claude', 'agents', `${createState.agentType}.md`)
|
|
2293
|
-
|
|
2294
|
-
try {
|
|
2295
|
-
// First, save the agent file
|
|
2296
|
-
await saveAgent(
|
|
2297
|
-
createState.location!,
|
|
2298
|
-
createState.agentType,
|
|
2299
|
-
createState.whenToUse,
|
|
2300
|
-
createState.selectedTools,
|
|
2301
|
-
createState.systemPrompt,
|
|
2302
|
-
createState.selectedModel,
|
|
2303
|
-
createState.selectedColor || undefined
|
|
2304
|
-
)
|
|
2305
|
-
|
|
2306
|
-
// Then open it in editor
|
|
2307
|
-
const command = process.platform === 'win32' ? 'start' :
|
|
2308
|
-
process.platform === 'darwin' ? 'open' : 'xdg-open'
|
|
2309
|
-
await execAsync(`${command} "${filePath}"`)
|
|
2310
|
-
onAgentCreated(`Created agent: ${createState.agentType}`)
|
|
2311
|
-
} catch (error) {
|
|
2312
|
-
setCreateState({ type: 'SET_ERROR', value: (error as Error).message })
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
useInput((input, key) => {
|
|
2317
|
-
if (isCreating) return
|
|
2318
|
-
|
|
2319
|
-
if ((key.return || input === 's') && !isCreating) {
|
|
2320
|
-
handleConfirm()
|
|
2321
|
-
} else if (input === 'e') {
|
|
2322
|
-
handleEditInEditor()
|
|
2323
|
-
} else if (key.escape) {
|
|
2324
|
-
setModeState({ mode: "create-color", location: createState.location! })
|
|
2325
|
-
}
|
|
2326
|
-
})
|
|
2327
|
-
|
|
2328
|
-
return (
|
|
2329
|
-
<Box flexDirection="column">
|
|
2330
|
-
<Header title="✅ Review & Create" subtitle="">
|
|
2331
|
-
<Box flexDirection="column" marginTop={1}>
|
|
2332
|
-
<Box marginBottom={1}>
|
|
2333
|
-
<Text bold color={theme.primary}>📋 Configuration</Text>
|
|
2334
|
-
</Box>
|
|
2335
|
-
|
|
2336
|
-
<Box flexDirection="column" gap={0}>
|
|
2337
|
-
<Text>• <Text bold>Agent ID:</Text> {createState.agentType}</Text>
|
|
2338
|
-
<Text>• <Text bold>Location:</Text> {createState.location === 'project' ? 'Project' : 'Personal'}</Text>
|
|
2339
|
-
<Text>• <Text bold>Tools:</Text> {toolNames.length > 50 ? toolNames.slice(0, 50) + '...' : toolNames}</Text>
|
|
2340
|
-
<Text>• <Text bold>Model:</Text> {getDisplayModelName(createState.selectedModel)}</Text>
|
|
2341
|
-
{createState.selectedColor && (
|
|
2342
|
-
<Text>• <Text bold>Color:</Text> <Text color={createState.selectedColor}>{createState.selectedColor}</Text></Text>
|
|
2343
|
-
)}
|
|
2344
|
-
</Box>
|
|
2345
|
-
|
|
2346
|
-
<Box marginTop={1} marginBottom={1}>
|
|
2347
|
-
<Text bold color={theme.primary}>📝 Purpose</Text>
|
|
2348
|
-
</Box>
|
|
2349
|
-
<Box paddingLeft={1}>
|
|
2350
|
-
<Text>{createState.whenToUse}</Text>
|
|
2351
|
-
</Box>
|
|
2352
|
-
|
|
2353
|
-
{validation.warnings.length > 0 && (
|
|
2354
|
-
<Box marginTop={1}>
|
|
2355
|
-
<Text><Text bold>Warnings:</Text></Text>
|
|
2356
|
-
{validation.warnings.map((warning, idx) => (
|
|
2357
|
-
<Fragment key={idx}>
|
|
2358
|
-
<Text color={theme.warning}> • {warning}</Text>
|
|
2359
|
-
</Fragment>
|
|
2360
|
-
))}
|
|
2361
|
-
</Box>
|
|
2362
|
-
)}
|
|
2363
|
-
|
|
2364
|
-
{createState.error && (
|
|
2365
|
-
<Box marginTop={1}>
|
|
2366
|
-
<Text color={theme.error}>✗ {createState.error}</Text>
|
|
2367
|
-
</Box>
|
|
2368
|
-
)}
|
|
2369
|
-
|
|
2370
|
-
<Box marginTop={2}>
|
|
2371
|
-
{isCreating ? (
|
|
2372
|
-
<LoadingSpinner text="Creating agent..." />
|
|
2373
|
-
) : null}
|
|
2374
|
-
</Box>
|
|
2375
|
-
</Box>
|
|
2376
|
-
</Header>
|
|
2377
|
-
<InstructionBar instructions="Enter Save • E Edit • Esc Back" />
|
|
2378
|
-
</Box>
|
|
2379
|
-
)
|
|
2380
|
-
}
|
|
2381
|
-
|
|
2382
|
-
// Step 1: Location selection
|
|
2383
|
-
interface LocationSelectProps {
|
|
2384
|
-
createState: CreateState
|
|
2385
|
-
setCreateState: React.Dispatch<any>
|
|
2386
|
-
setModeState: (state: ModeState) => void
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
function LocationSelect({ createState, setCreateState, setModeState }: LocationSelectProps) {
|
|
2390
|
-
const theme = getTheme()
|
|
2391
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2392
|
-
|
|
2393
|
-
const options = [
|
|
2394
|
-
{ label: "📁 Project", value: "project", desc: ".claude/agents/" },
|
|
2395
|
-
{ label: "🏠 Personal", value: "user", desc: "~/.claude/agents/" }
|
|
2396
|
-
]
|
|
2397
|
-
|
|
2398
|
-
const handleChange = (value: string) => {
|
|
2399
|
-
setCreateState({ type: 'SET_LOCATION', value: value as AgentLocation })
|
|
2400
|
-
setCreateState({ type: 'SET_METHOD', value: 'generate' }) // Always use generate method
|
|
2401
|
-
setModeState({ mode: "create-generate", location: value as AgentLocation })
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
const handleCancel = () => {
|
|
2405
|
-
setModeState({ mode: "list-agents", location: "all" as AgentLocation })
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
useInput((input, key) => {
|
|
2409
|
-
if (key.escape) {
|
|
2410
|
-
handleCancel()
|
|
2411
|
-
} else if (key.return) {
|
|
2412
|
-
handleChange(options[selectedIndex].value)
|
|
2413
|
-
} else if (key.upArrow) {
|
|
2414
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : options.length - 1)
|
|
2415
|
-
} else if (key.downArrow) {
|
|
2416
|
-
setSelectedIndex(prev => prev < options.length - 1 ? prev + 1 : 0)
|
|
2417
|
-
}
|
|
2418
|
-
})
|
|
2419
|
-
|
|
2420
|
-
return (
|
|
2421
|
-
<Box flexDirection="column">
|
|
2422
|
-
<Header title="📦 Save Location" subtitle="" step={1} totalSteps={5}>
|
|
2423
|
-
<Box marginTop={1} flexDirection="column">
|
|
2424
|
-
{options.map((opt, idx) => (
|
|
2425
|
-
<Box key={opt.value} flexDirection="column" marginBottom={1}>
|
|
2426
|
-
<Text color={idx === selectedIndex ? theme.primary : undefined}>
|
|
2427
|
-
{idx === selectedIndex ? '❯ ' : ' '}{opt.label}
|
|
2428
|
-
</Text>
|
|
2429
|
-
<Box marginLeft={3}>
|
|
2430
|
-
<Text dimColor>{opt.desc}</Text>
|
|
2431
|
-
</Box>
|
|
2432
|
-
</Box>
|
|
2433
|
-
))}
|
|
2434
|
-
</Box>
|
|
2435
|
-
</Header>
|
|
2436
|
-
<InstructionBar instructions="↑↓ Navigate • Enter Select" />
|
|
2437
|
-
</Box>
|
|
2438
|
-
)
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
// Step 2: Method selection
|
|
2442
|
-
interface MethodSelectProps {
|
|
2443
|
-
createState: CreateState
|
|
2444
|
-
setCreateState: React.Dispatch<any>
|
|
2445
|
-
setModeState: (state: ModeState) => void
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
function MethodSelect({ createState, setCreateState, setModeState }: MethodSelectProps) {
|
|
2449
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2450
|
-
|
|
2451
|
-
const options = [
|
|
2452
|
-
{ label: "Generate with Claude (recommended)", value: "generate" },
|
|
2453
|
-
{ label: "Manual configuration", value: "manual" }
|
|
2454
|
-
]
|
|
2455
|
-
|
|
2456
|
-
const handleChange = (value: string) => {
|
|
2457
|
-
setCreateState({ type: 'SET_METHOD', value: value as 'generate' | 'manual' })
|
|
2458
|
-
if (value === "generate") {
|
|
2459
|
-
setCreateState({ type: 'SET_IS_AI_GENERATED', value: true })
|
|
2460
|
-
setModeState({ mode: "create-generate", location: createState.location })
|
|
2461
|
-
} else {
|
|
2462
|
-
setCreateState({ type: 'SET_IS_AI_GENERATED', value: false })
|
|
2463
|
-
setModeState({ mode: "create-type", location: createState.location })
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
const handleCancel = () => {
|
|
2468
|
-
setModeState({ mode: "create-location" })
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
useInput((input, key) => {
|
|
2472
|
-
if (key.escape) {
|
|
2473
|
-
handleCancel()
|
|
2474
|
-
} else if (key.return) {
|
|
2475
|
-
handleChange(options[selectedIndex].value)
|
|
2476
|
-
} else if (key.upArrow) {
|
|
2477
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : options.length - 1)
|
|
2478
|
-
} else if (key.downArrow) {
|
|
2479
|
-
setSelectedIndex(prev => prev < options.length - 1 ? prev + 1 : 0)
|
|
2480
|
-
}
|
|
2481
|
-
})
|
|
2482
|
-
|
|
2483
|
-
return (
|
|
2484
|
-
<Box flexDirection="column">
|
|
2485
|
-
<Header title="Create new agent" subtitle="Creation method" step={2} totalSteps={9}>
|
|
2486
|
-
<Box marginTop={1}>
|
|
2487
|
-
<SelectList
|
|
2488
|
-
options={options}
|
|
2489
|
-
selectedIndex={selectedIndex}
|
|
2490
|
-
onChange={handleChange}
|
|
2491
|
-
onCancel={handleCancel}
|
|
2492
|
-
/>
|
|
2493
|
-
</Box>
|
|
2494
|
-
</Header>
|
|
2495
|
-
<InstructionBar />
|
|
2496
|
-
</Box>
|
|
2497
|
-
)
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
// Agent menu for agent operations
|
|
2501
|
-
interface AgentMenuProps {
|
|
2502
|
-
agent: AgentConfig
|
|
2503
|
-
setModeState: (state: ModeState) => void
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
function AgentMenu({ agent, setModeState }: AgentMenuProps) {
|
|
2507
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2508
|
-
|
|
2509
|
-
const options = [
|
|
2510
|
-
{ label: "View details", value: "view" },
|
|
2511
|
-
{ label: "Edit agent", value: "edit", disabled: agent.location === 'built-in' },
|
|
2512
|
-
{ label: "Delete agent", value: "delete", disabled: agent.location === 'built-in' }
|
|
2513
|
-
]
|
|
2514
|
-
|
|
2515
|
-
const availableOptions = options.filter(opt => !opt.disabled)
|
|
2516
|
-
|
|
2517
|
-
const handleSelect = (value: string) => {
|
|
2518
|
-
switch (value) {
|
|
2519
|
-
case "view":
|
|
2520
|
-
setModeState({ mode: "view-agent", selectedAgent: agent })
|
|
2521
|
-
break
|
|
2522
|
-
case "edit":
|
|
2523
|
-
setModeState({ mode: "edit-agent", selectedAgent: agent })
|
|
2524
|
-
break
|
|
2525
|
-
case "delete":
|
|
2526
|
-
setModeState({ mode: "delete-confirm", selectedAgent: agent })
|
|
2527
|
-
break
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
|
-
useInput((input, key) => {
|
|
2532
|
-
if (key.return) {
|
|
2533
|
-
handleSelect(availableOptions[selectedIndex].value)
|
|
2534
|
-
} else if (key.upArrow) {
|
|
2535
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : availableOptions.length - 1)
|
|
2536
|
-
} else if (key.downArrow) {
|
|
2537
|
-
setSelectedIndex(prev => prev < availableOptions.length - 1 ? prev + 1 : 0)
|
|
2538
|
-
}
|
|
2539
|
-
})
|
|
2540
|
-
|
|
2541
|
-
return (
|
|
2542
|
-
<Box flexDirection="column">
|
|
2543
|
-
<Header title={`Agent: ${agent.agentType}`} subtitle={`${agent.location}`}>
|
|
2544
|
-
<Box marginTop={1}>
|
|
2545
|
-
<SelectList
|
|
2546
|
-
options={availableOptions}
|
|
2547
|
-
selectedIndex={selectedIndex}
|
|
2548
|
-
onChange={handleSelect}
|
|
2549
|
-
numbered={false}
|
|
2550
|
-
/>
|
|
2551
|
-
</Box>
|
|
2552
|
-
</Header>
|
|
2553
|
-
<InstructionBar />
|
|
2554
|
-
</Box>
|
|
2555
|
-
)
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
// Edit menu for agent editing options
|
|
2559
|
-
interface EditMenuProps {
|
|
2560
|
-
agent: AgentConfig
|
|
2561
|
-
setModeState: (state: ModeState) => void
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
|
-
function EditMenu({ agent, setModeState }: EditMenuProps) {
|
|
2565
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2566
|
-
const [isOpening, setIsOpening] = useState(false)
|
|
2567
|
-
const theme = getTheme()
|
|
2568
|
-
|
|
2569
|
-
const options = [
|
|
2570
|
-
{ label: "Open in editor", value: "open-editor" },
|
|
2571
|
-
{ label: "Edit tools", value: "edit-tools" },
|
|
2572
|
-
{ label: "Edit model", value: "edit-model" },
|
|
2573
|
-
{ label: "Edit color", value: "edit-color" }
|
|
2574
|
-
]
|
|
2575
|
-
|
|
2576
|
-
const handleSelect = async (value: string) => {
|
|
2577
|
-
switch (value) {
|
|
2578
|
-
case "open-editor":
|
|
2579
|
-
setIsOpening(true)
|
|
2580
|
-
try {
|
|
2581
|
-
const filePath = getAgentFilePath(agent)
|
|
2582
|
-
await openInEditor(filePath)
|
|
2583
|
-
setModeState({ mode: "agent-menu", selectedAgent: agent })
|
|
2584
|
-
} catch (error) {
|
|
2585
|
-
console.error('Failed to open editor:', error)
|
|
2586
|
-
// TODO: Show error to user
|
|
2587
|
-
} finally {
|
|
2588
|
-
setIsOpening(false)
|
|
2589
|
-
}
|
|
2590
|
-
break
|
|
2591
|
-
case "edit-tools":
|
|
2592
|
-
setModeState({ mode: "edit-tools", selectedAgent: agent })
|
|
2593
|
-
break
|
|
2594
|
-
case "edit-model":
|
|
2595
|
-
setModeState({ mode: "edit-model", selectedAgent: agent })
|
|
2596
|
-
break
|
|
2597
|
-
case "edit-color":
|
|
2598
|
-
setModeState({ mode: "edit-color", selectedAgent: agent })
|
|
2599
|
-
break
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
const handleBack = () => {
|
|
2604
|
-
setModeState({ mode: "agent-menu", selectedAgent: agent })
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
useInput((input, key) => {
|
|
2608
|
-
if (key.escape) {
|
|
2609
|
-
handleBack()
|
|
2610
|
-
} else if (key.return && !isOpening) {
|
|
2611
|
-
handleSelect(options[selectedIndex].value)
|
|
2612
|
-
} else if (key.upArrow) {
|
|
2613
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : options.length - 1)
|
|
2614
|
-
} else if (key.downArrow) {
|
|
2615
|
-
setSelectedIndex(prev => prev < options.length - 1 ? prev + 1 : 0)
|
|
2616
|
-
}
|
|
2617
|
-
})
|
|
2618
|
-
|
|
2619
|
-
if (isOpening) {
|
|
2620
|
-
return (
|
|
2621
|
-
<Box flexDirection="column">
|
|
2622
|
-
<Header title={`Edit agent: ${agent.agentType}`} subtitle="Opening in editor...">
|
|
2623
|
-
<Box marginTop={1}>
|
|
2624
|
-
<LoadingSpinner text="Opening file in editor..." />
|
|
2625
|
-
</Box>
|
|
2626
|
-
</Header>
|
|
2627
|
-
<InstructionBar />
|
|
2628
|
-
</Box>
|
|
2629
|
-
)
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
return (
|
|
2633
|
-
<Box flexDirection="column">
|
|
2634
|
-
<Header title={`Edit agent: ${agent.agentType}`} subtitle={`Location: ${agent.location}`}>
|
|
2635
|
-
<Box marginTop={1}>
|
|
2636
|
-
<SelectList
|
|
2637
|
-
options={options}
|
|
2638
|
-
selectedIndex={selectedIndex}
|
|
2639
|
-
onChange={handleSelect}
|
|
2640
|
-
numbered={false}
|
|
2641
|
-
/>
|
|
2642
|
-
</Box>
|
|
2643
|
-
</Header>
|
|
2644
|
-
<InstructionBar instructions="↑↓ navigate · Enter select · Esc back" />
|
|
2645
|
-
</Box>
|
|
2646
|
-
)
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
// Edit tools step
|
|
2650
|
-
interface EditToolsStepProps {
|
|
2651
|
-
agent: AgentConfig
|
|
2652
|
-
tools: Tool[]
|
|
2653
|
-
setModeState: (state: ModeState) => void
|
|
2654
|
-
onAgentUpdated: (message: string, updated: AgentConfig) => void
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
function EditToolsStep({ agent, tools, setModeState, onAgentUpdated }: EditToolsStepProps) {
|
|
2658
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
2659
|
-
|
|
2660
|
-
// Initialize selected tools based on agent.tools
|
|
2661
|
-
const initialTools = Array.isArray(agent.tools) ? agent.tools :
|
|
2662
|
-
agent.tools === '*' ? tools.map(t => t.name) : []
|
|
2663
|
-
const [selectedTools, setSelectedTools] = useState<Set<string>>(new Set(initialTools))
|
|
2664
|
-
const [showAdvanced, setShowAdvanced] = useState(false)
|
|
2665
|
-
const [isUpdating, setIsUpdating] = useState(false)
|
|
2666
|
-
|
|
2667
|
-
// Categorize tools
|
|
2668
|
-
const categorizedTools = useMemo(() => {
|
|
2669
|
-
const categories: Record<string, Tool[]> = {
|
|
2670
|
-
read: [],
|
|
2671
|
-
edit: [],
|
|
2672
|
-
execution: [],
|
|
2673
|
-
web: [],
|
|
2674
|
-
other: []
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
tools.forEach(tool => {
|
|
2678
|
-
let categorized = false
|
|
2679
|
-
|
|
2680
|
-
// Check built-in categories
|
|
2681
|
-
for (const [category, toolNames] of Object.entries(TOOL_CATEGORIES)) {
|
|
2682
|
-
if (Array.isArray(toolNames) && toolNames.includes(tool.name)) {
|
|
2683
|
-
categories[category as keyof typeof categories]?.push(tool)
|
|
2684
|
-
categorized = true
|
|
2685
|
-
break
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
if (!categorized) {
|
|
2690
|
-
categories.other.push(tool)
|
|
2691
|
-
}
|
|
2692
|
-
})
|
|
2693
|
-
|
|
2694
|
-
return categories
|
|
2695
|
-
}, [tools])
|
|
2696
|
-
|
|
2697
|
-
const allSelected = selectedTools.size === tools.length && tools.length > 0
|
|
2698
|
-
const readSelected = categorizedTools.read.every(tool => selectedTools.has(tool.name)) && categorizedTools.read.length > 0
|
|
2699
|
-
const editSelected = categorizedTools.edit.every(tool => selectedTools.has(tool.name)) && categorizedTools.edit.length > 0
|
|
2700
|
-
const execSelected = categorizedTools.execution.every(tool => selectedTools.has(tool.name)) && categorizedTools.execution.length > 0
|
|
2701
|
-
|
|
2702
|
-
const options = [
|
|
2703
|
-
{ id: 'continue', label: 'Save', isContinue: true },
|
|
2704
|
-
{ id: 'separator1', label: '────────────────────────────────────', isSeparator: true },
|
|
2705
|
-
{ id: 'all', label: `${allSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} All tools`, isAll: true },
|
|
2706
|
-
{ id: 'read', label: `${readSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Read-only tools`, isCategory: true },
|
|
2707
|
-
{ id: 'edit', label: `${editSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Edit tools`, isCategory: true },
|
|
2708
|
-
{ id: 'execution', label: `${execSelected ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} Execution tools`, isCategory: true },
|
|
2709
|
-
{ id: 'separator2', label: '────────────────────────────────────', isSeparator: true },
|
|
2710
|
-
{ id: 'advanced', label: `[ ${showAdvanced ? 'Hide' : 'Show'} advanced options ]`, isAdvancedToggle: true },
|
|
2711
|
-
...(showAdvanced ? tools.map(tool => ({
|
|
2712
|
-
id: tool.name,
|
|
2713
|
-
label: `${selectedTools.has(tool.name) ? UI_ICONS.checkboxOn : UI_ICONS.checkboxOff} ${tool.name}`,
|
|
2714
|
-
isTool: true
|
|
2715
|
-
})) : [])
|
|
2716
|
-
]
|
|
2717
|
-
|
|
2718
|
-
const handleSave = async () => {
|
|
2719
|
-
setIsUpdating(true)
|
|
2720
|
-
try {
|
|
2721
|
-
// Type-safe tools conversion for updateAgent
|
|
2722
|
-
const toolsArray: string[] | '*' = allSelected ? '*' : Array.from(selectedTools)
|
|
2723
|
-
await updateAgent(agent, agent.whenToUse, toolsArray, agent.systemPrompt, agent.color, (agent as any).model)
|
|
2724
|
-
|
|
2725
|
-
// Clear cache and reload fresh agent data from file system
|
|
2726
|
-
clearAgentCache()
|
|
2727
|
-
const freshAgents = await getActiveAgents()
|
|
2728
|
-
const updatedAgent = freshAgents.find(a => a.agentType === agent.agentType)
|
|
2729
|
-
|
|
2730
|
-
if (updatedAgent) {
|
|
2731
|
-
onAgentUpdated(`Updated tools for agent: ${agent.agentType}`, updatedAgent)
|
|
2732
|
-
setModeState({ mode: "edit-agent", selectedAgent: updatedAgent })
|
|
2733
|
-
} else {
|
|
2734
|
-
console.error('Failed to find updated agent after save')
|
|
2735
|
-
// Fallback to manual update
|
|
2736
|
-
const fallbackAgent: AgentConfig = {
|
|
2737
|
-
...agent,
|
|
2738
|
-
tools: toolsArray.length === 1 && toolsArray[0] === '*' ? '*' : toolsArray,
|
|
2739
|
-
}
|
|
2740
|
-
onAgentUpdated(`Updated tools for agent: ${agent.agentType}`, fallbackAgent)
|
|
2741
|
-
setModeState({ mode: "edit-agent", selectedAgent: fallbackAgent })
|
|
2742
|
-
}
|
|
2743
|
-
} catch (error) {
|
|
2744
|
-
console.error('Failed to update agent tools:', error)
|
|
2745
|
-
// TODO: Show error to user
|
|
2746
|
-
} finally {
|
|
2747
|
-
setIsUpdating(false)
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
const handleSelect = () => {
|
|
2752
|
-
const option = options[selectedIndex] as any // Type assertion for union type
|
|
2753
|
-
if (!option) return
|
|
2754
|
-
if (option.isSeparator) return
|
|
2755
|
-
|
|
2756
|
-
if (option.isContinue) {
|
|
2757
|
-
handleSave()
|
|
2758
|
-
} else if (option.isAdvancedToggle) {
|
|
2759
|
-
setShowAdvanced(!showAdvanced)
|
|
2760
|
-
} else if (option.isAll) {
|
|
2761
|
-
if (allSelected) {
|
|
2762
|
-
setSelectedTools(new Set())
|
|
2763
|
-
} else {
|
|
2764
|
-
setSelectedTools(new Set(tools.map(t => t.name)))
|
|
2765
|
-
}
|
|
2766
|
-
} else if (option.isCategory) {
|
|
2767
|
-
const categoryName = option.id as keyof typeof categorizedTools
|
|
2768
|
-
const categoryTools = categorizedTools[categoryName] || []
|
|
2769
|
-
const newSelected = new Set(selectedTools)
|
|
2770
|
-
|
|
2771
|
-
const categorySelected = categoryTools.every(tool => selectedTools.has(tool.name))
|
|
2772
|
-
if (categorySelected) {
|
|
2773
|
-
categoryTools.forEach(tool => newSelected.delete(tool.name))
|
|
2774
|
-
} else {
|
|
2775
|
-
categoryTools.forEach(tool => newSelected.add(tool.name))
|
|
2776
|
-
}
|
|
2777
|
-
setSelectedTools(newSelected)
|
|
2778
|
-
} else if (option.isTool) {
|
|
2779
|
-
const newSelected = new Set(selectedTools)
|
|
2780
|
-
if (newSelected.has(option.id)) {
|
|
2781
|
-
newSelected.delete(option.id)
|
|
2782
|
-
} else {
|
|
2783
|
-
newSelected.add(option.id)
|
|
2784
|
-
}
|
|
2785
|
-
setSelectedTools(newSelected)
|
|
2786
|
-
}
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
useInput((input, key) => {
|
|
2790
|
-
if (key.escape) {
|
|
2791
|
-
setModeState({ mode: "edit-agent", selectedAgent: agent })
|
|
2792
|
-
} else if (key.return && !isUpdating) {
|
|
2793
|
-
handleSelect()
|
|
2794
|
-
} else if (key.upArrow) {
|
|
2795
|
-
setSelectedIndex(prev => {
|
|
2796
|
-
let newIndex = prev > 0 ? prev - 1 : options.length - 1
|
|
2797
|
-
// Skip separators when going up
|
|
2798
|
-
while (options[newIndex] && (options[newIndex] as any).isSeparator) {
|
|
2799
|
-
newIndex = newIndex > 0 ? newIndex - 1 : options.length - 1
|
|
2800
|
-
}
|
|
2801
|
-
return newIndex
|
|
2802
|
-
})
|
|
2803
|
-
} else if (key.downArrow) {
|
|
2804
|
-
setSelectedIndex(prev => {
|
|
2805
|
-
let newIndex = prev < options.length - 1 ? prev + 1 : 0
|
|
2806
|
-
// Skip separators when going down
|
|
2807
|
-
while (options[newIndex] && (options[newIndex] as any).isSeparator) {
|
|
2808
|
-
newIndex = newIndex < options.length - 1 ? newIndex + 1 : 0
|
|
2809
|
-
}
|
|
2810
|
-
return newIndex
|
|
2811
|
-
})
|
|
2812
|
-
}
|
|
2813
|
-
})
|
|
2814
|
-
|
|
2815
|
-
if (isUpdating) {
|
|
2816
|
-
return (
|
|
2817
|
-
<Box flexDirection="column">
|
|
2818
|
-
<Header title={`Edit agent: ${agent.agentType}`}>
|
|
2819
|
-
<Box marginTop={1}>
|
|
2820
|
-
<LoadingSpinner text="Updating agent tools..." />
|
|
2821
|
-
</Box>
|
|
2822
|
-
</Header>
|
|
2823
|
-
<InstructionBar />
|
|
2824
|
-
</Box>
|
|
2825
|
-
)
|
|
2826
|
-
}
|
|
2827
|
-
|
|
2828
|
-
return (
|
|
2829
|
-
<Box flexDirection="column">
|
|
2830
|
-
<Header title={`Edit agent: ${agent.agentType}`}>
|
|
2831
|
-
<Box flexDirection="column" marginTop={1}>
|
|
2832
|
-
{options.map((option, idx) => {
|
|
2833
|
-
const isSelected = idx === selectedIndex
|
|
2834
|
-
const isContinue = 'isContinue' in option && option.isContinue
|
|
2835
|
-
const isAdvancedToggle = (option as any).isAdvancedToggle
|
|
2836
|
-
const isSeparator = (option as any).isSeparator
|
|
2837
|
-
|
|
2838
|
-
return (
|
|
2839
|
-
<Box key={option.id}>
|
|
2840
|
-
<Text
|
|
2841
|
-
color={isSelected && !isSeparator ? 'cyan' : isSeparator ? 'gray' : undefined}
|
|
2842
|
-
bold={isContinue}
|
|
2843
|
-
dimColor={isSeparator}
|
|
2844
|
-
>
|
|
2845
|
-
{isSeparator ?
|
|
2846
|
-
option.label :
|
|
2847
|
-
`${isSelected ? `${UI_ICONS.pointer} ` : ' '}${isContinue || isAdvancedToggle ? option.label : option.label}`
|
|
2848
|
-
}
|
|
2849
|
-
</Text>
|
|
2850
|
-
{(option as any).isTool && isSelected && tools.find(t => t.name === option.id)?.description && (
|
|
2851
|
-
<Box marginLeft={4}>
|
|
2852
|
-
<Text dimColor>{tools.find(t => t.name === option.id)?.description}</Text>
|
|
2853
|
-
</Box>
|
|
2854
|
-
)}
|
|
2855
|
-
</Box>
|
|
2856
|
-
)
|
|
2857
|
-
})}
|
|
2858
|
-
|
|
2859
|
-
<Box marginTop={1}>
|
|
2860
|
-
<Text dimColor>
|
|
2861
|
-
{allSelected ?
|
|
2862
|
-
'All tools selected' :
|
|
2863
|
-
`${selectedTools.size} of ${tools.length} tools selected`}
|
|
2864
|
-
</Text>
|
|
2865
|
-
</Box>
|
|
2866
|
-
</Box>
|
|
2867
|
-
</Header>
|
|
2868
|
-
<InstructionBar instructions="Enter toggle selection · ↑↓ navigate · Esc back" />
|
|
2869
|
-
</Box>
|
|
2870
|
-
)
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
// Edit model step
|
|
2874
|
-
interface EditModelStepProps {
|
|
2875
|
-
agent: AgentConfig
|
|
2876
|
-
setModeState: (state: ModeState) => void
|
|
2877
|
-
onAgentUpdated: (message: string, updated: AgentConfig) => void
|
|
2878
|
-
}
|
|
2879
|
-
|
|
2880
|
-
function EditModelStep({ agent, setModeState, onAgentUpdated }: EditModelStepProps) {
|
|
2881
|
-
const manager = getModelManager()
|
|
2882
|
-
const profiles = manager.getActiveModelProfiles()
|
|
2883
|
-
const currentModel = (agent as any).model || null
|
|
2884
|
-
|
|
2885
|
-
// Build model options array
|
|
2886
|
-
const modelOptions = [
|
|
2887
|
-
{ id: null, name: 'Inherit from parent', description: 'Use the model from task configuration' },
|
|
2888
|
-
...profiles.map((p: any) => ({ id: p.modelName, name: p.name, description: `${p.provider || 'provider'} · ${p.modelName}` }))
|
|
2889
|
-
]
|
|
2890
|
-
|
|
2891
|
-
// Find the index of current model
|
|
2892
|
-
const defaultIndex = modelOptions.findIndex(m => m.id === currentModel)
|
|
2893
|
-
const [selectedIndex, setSelectedIndex] = useState(defaultIndex >= 0 ? defaultIndex : 0)
|
|
2894
|
-
const [isUpdating, setIsUpdating] = useState(false)
|
|
2895
|
-
|
|
2896
|
-
const handleSave = async (modelId: string | null) => {
|
|
2897
|
-
setIsUpdating(true)
|
|
2898
|
-
try {
|
|
2899
|
-
const modelValue = modelId === null ? undefined : modelId
|
|
2900
|
-
await updateAgent(agent, agent.whenToUse, agent.tools, agent.systemPrompt, agent.color, modelValue)
|
|
2901
|
-
|
|
2902
|
-
// Clear cache and reload fresh agent data from file system
|
|
2903
|
-
clearAgentCache()
|
|
2904
|
-
const freshAgents = await getActiveAgents()
|
|
2905
|
-
const updatedAgent = freshAgents.find(a => a.agentType === agent.agentType)
|
|
2906
|
-
|
|
2907
|
-
if (updatedAgent) {
|
|
2908
|
-
onAgentUpdated(`Updated model for agent: ${agent.agentType}`, updatedAgent)
|
|
2909
|
-
setModeState({ mode: 'edit-agent', selectedAgent: updatedAgent })
|
|
2910
|
-
} else {
|
|
2911
|
-
console.error('Failed to find updated agent after save')
|
|
2912
|
-
// Fallback to manual update
|
|
2913
|
-
const fallbackAgent: AgentConfig = { ...agent }
|
|
2914
|
-
if (modelValue) {
|
|
2915
|
-
(fallbackAgent as any).model = modelValue
|
|
2916
|
-
} else {
|
|
2917
|
-
delete (fallbackAgent as any).model
|
|
2918
|
-
}
|
|
2919
|
-
onAgentUpdated(`Updated model for agent: ${agent.agentType}`, fallbackAgent)
|
|
2920
|
-
setModeState({ mode: 'edit-agent', selectedAgent: fallbackAgent })
|
|
2921
|
-
}
|
|
2922
|
-
} catch (error) {
|
|
2923
|
-
console.error('Failed to update agent model:', error)
|
|
2924
|
-
} finally {
|
|
2925
|
-
setIsUpdating(false)
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
useInput((input, key) => {
|
|
2930
|
-
if (key.escape) {
|
|
2931
|
-
setModeState({ mode: 'edit-agent', selectedAgent: agent })
|
|
2932
|
-
} else if (key.return && !isUpdating) {
|
|
2933
|
-
handleSave(modelOptions[selectedIndex].id)
|
|
2934
|
-
} else if (key.upArrow) {
|
|
2935
|
-
setSelectedIndex(prev => (prev > 0 ? prev - 1 : modelOptions.length - 1))
|
|
2936
|
-
} else if (key.downArrow) {
|
|
2937
|
-
setSelectedIndex(prev => (prev < modelOptions.length - 1 ? prev + 1 : 0))
|
|
2938
|
-
}
|
|
2939
|
-
})
|
|
2940
|
-
|
|
2941
|
-
if (isUpdating) {
|
|
2942
|
-
return (
|
|
2943
|
-
<Box flexDirection="column">
|
|
2944
|
-
<Header title={`Edit agent: ${agent.agentType}`}>
|
|
2945
|
-
<Box marginTop={1}>
|
|
2946
|
-
<LoadingSpinner text="Updating agent model..." />
|
|
2947
|
-
</Box>
|
|
2948
|
-
</Header>
|
|
2949
|
-
<InstructionBar />
|
|
2950
|
-
</Box>
|
|
2951
|
-
)
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
return (
|
|
2955
|
-
<Box flexDirection="column">
|
|
2956
|
-
<Header title={`Edit agent: ${agent.agentType}`} subtitle="Model determines the agent's reasoning capabilities and speed.">
|
|
2957
|
-
<Box marginTop={2}>
|
|
2958
|
-
<SelectList
|
|
2959
|
-
options={modelOptions.map((m, i) => ({ label: `${i + 1}. ${m.name}${m.description ? `\n${m.description}` : ''}`, value: m.id }))}
|
|
2960
|
-
selectedIndex={selectedIndex}
|
|
2961
|
-
onChange={(val) => handleSave(val)}
|
|
2962
|
-
numbered={false}
|
|
2963
|
-
/>
|
|
2964
|
-
</Box>
|
|
2965
|
-
</Header>
|
|
2966
|
-
<InstructionBar instructions="↑↓ navigate · Enter select · Esc back" />
|
|
2967
|
-
</Box>
|
|
2968
|
-
)
|
|
2969
|
-
}
|
|
2970
|
-
|
|
2971
|
-
// Edit color step
|
|
2972
|
-
interface EditColorStepProps {
|
|
2973
|
-
agent: AgentConfig
|
|
2974
|
-
setModeState: (state: ModeState) => void
|
|
2975
|
-
onAgentUpdated: (message: string, updated: AgentConfig) => void
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
function EditColorStep({ agent, setModeState, onAgentUpdated }: EditColorStepProps) {
|
|
2979
|
-
const currentColor = agent.color || null
|
|
2980
|
-
|
|
2981
|
-
// Define color options (removed red/green due to display issues)
|
|
2982
|
-
const colors = [
|
|
2983
|
-
{ label: 'Automatic color', value: null },
|
|
2984
|
-
{ label: 'Yellow', value: 'yellow' },
|
|
2985
|
-
{ label: 'Blue', value: 'blue' },
|
|
2986
|
-
{ label: 'Magenta', value: 'magenta' },
|
|
2987
|
-
{ label: 'Cyan', value: 'cyan' },
|
|
2988
|
-
{ label: 'Gray', value: 'gray' },
|
|
2989
|
-
{ label: 'White', value: 'white' }
|
|
2990
|
-
]
|
|
2991
|
-
|
|
2992
|
-
// Find current color index
|
|
2993
|
-
const defaultIndex = colors.findIndex(color => color.value === currentColor)
|
|
2994
|
-
const [selectedIndex, setSelectedIndex] = useState(defaultIndex >= 0 ? defaultIndex : 0)
|
|
2995
|
-
const [isUpdating, setIsUpdating] = useState(false)
|
|
2996
|
-
|
|
2997
|
-
const handleSave = async (color: string | null) => {
|
|
2998
|
-
setIsUpdating(true)
|
|
2999
|
-
try {
|
|
3000
|
-
const colorValue = color === null ? undefined : color
|
|
3001
|
-
await updateAgent(agent, agent.whenToUse, agent.tools, agent.systemPrompt, colorValue, (agent as any).model)
|
|
3002
|
-
|
|
3003
|
-
// Clear cache and reload fresh agent data from file system
|
|
3004
|
-
clearAgentCache()
|
|
3005
|
-
const freshAgents = await getActiveAgents()
|
|
3006
|
-
const updatedAgent = freshAgents.find(a => a.agentType === agent.agentType)
|
|
3007
|
-
|
|
3008
|
-
if (updatedAgent) {
|
|
3009
|
-
onAgentUpdated(`Updated color for agent: ${agent.agentType}`, updatedAgent)
|
|
3010
|
-
setModeState({ mode: "edit-agent", selectedAgent: updatedAgent })
|
|
3011
|
-
} else {
|
|
3012
|
-
console.error('Failed to find updated agent after save')
|
|
3013
|
-
// Fallback to manual update
|
|
3014
|
-
const fallbackAgent: AgentConfig = { ...agent, ...(colorValue ? { color: colorValue } : { color: undefined }) }
|
|
3015
|
-
onAgentUpdated(`Updated color for agent: ${agent.agentType}`, fallbackAgent)
|
|
3016
|
-
setModeState({ mode: "edit-agent", selectedAgent: fallbackAgent })
|
|
3017
|
-
}
|
|
3018
|
-
} catch (error) {
|
|
3019
|
-
console.error('Failed to update agent color:', error)
|
|
3020
|
-
// TODO: Show error to user
|
|
3021
|
-
} finally {
|
|
3022
|
-
setIsUpdating(false)
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
|
|
3026
|
-
useInput((input, key) => {
|
|
3027
|
-
if (key.escape) {
|
|
3028
|
-
setModeState({ mode: "edit-agent", selectedAgent: agent })
|
|
3029
|
-
} else if (key.return && !isUpdating) {
|
|
3030
|
-
handleSave(colors[selectedIndex].value)
|
|
3031
|
-
} else if (key.upArrow) {
|
|
3032
|
-
setSelectedIndex(prev => prev > 0 ? prev - 1 : colors.length - 1)
|
|
3033
|
-
} else if (key.downArrow) {
|
|
3034
|
-
setSelectedIndex(prev => prev < colors.length - 1 ? prev + 1 : 0)
|
|
3035
|
-
}
|
|
3036
|
-
})
|
|
3037
|
-
|
|
3038
|
-
if (isUpdating) {
|
|
3039
|
-
return (
|
|
3040
|
-
<Box flexDirection="column">
|
|
3041
|
-
<Header title={`Edit agent: ${agent.agentType}`}>
|
|
3042
|
-
<Box marginTop={1}>
|
|
3043
|
-
<LoadingSpinner text="Updating agent color..." />
|
|
3044
|
-
</Box>
|
|
3045
|
-
</Header>
|
|
3046
|
-
<InstructionBar />
|
|
3047
|
-
</Box>
|
|
3048
|
-
)
|
|
3049
|
-
}
|
|
3050
|
-
|
|
3051
|
-
const selectedColor = colors[selectedIndex]
|
|
3052
|
-
const previewColor = selectedColor.value || undefined
|
|
3053
|
-
|
|
3054
|
-
return (
|
|
3055
|
-
<Box flexDirection="column">
|
|
3056
|
-
<Header title={`Edit agent: ${agent.agentType}`} subtitle="Choose background color">
|
|
3057
|
-
<Box flexDirection="column" marginTop={1}>
|
|
3058
|
-
{colors.map((color, index) => {
|
|
3059
|
-
const isSelected = index === selectedIndex
|
|
3060
|
-
const isCurrent = color.value === currentColor
|
|
3061
|
-
|
|
3062
|
-
return (
|
|
3063
|
-
<Box key={color.value || 'automatic'}>
|
|
3064
|
-
<Text color={isSelected ? 'cyan' : undefined}>
|
|
3065
|
-
{isSelected ? '❯ ' : ' '}
|
|
3066
|
-
</Text>
|
|
3067
|
-
<Text color={color.value || undefined}>●</Text>
|
|
3068
|
-
<Text>
|
|
3069
|
-
{' '}{color.label}
|
|
3070
|
-
{isCurrent && (
|
|
3071
|
-
<Text color="green"> ✔</Text>
|
|
3072
|
-
)}
|
|
3073
|
-
</Text>
|
|
3074
|
-
</Box>
|
|
3075
|
-
)
|
|
3076
|
-
})}
|
|
3077
|
-
|
|
3078
|
-
<Box marginTop={2}>
|
|
3079
|
-
<Text>Preview: </Text>
|
|
3080
|
-
<Text color={previewColor}>{agent.agentType}</Text>
|
|
3081
|
-
</Box>
|
|
3082
|
-
</Box>
|
|
3083
|
-
</Header>
|
|
3084
|
-
<InstructionBar instructions="↑↓ navigate · Enter select · Esc back" />
|
|
3085
|
-
</Box>
|
|
3086
|
-
)
|
|
3087
|
-
}
|
|
3088
|
-
|
|
3089
|
-
// View agent details
|
|
3090
|
-
interface ViewAgentProps {
|
|
3091
|
-
agent: AgentConfig
|
|
3092
|
-
tools: Tool[]
|
|
3093
|
-
setModeState: (state: ModeState) => void
|
|
3094
|
-
}
|
|
3095
|
-
|
|
3096
|
-
function ViewAgent({ agent, tools, setModeState }: ViewAgentProps) {
|
|
3097
|
-
const theme = getTheme()
|
|
3098
|
-
const agentTools = Array.isArray(agent.tools) ? agent.tools : []
|
|
3099
|
-
const hasAllTools = agent.tools === "*" || agentTools.includes("*")
|
|
3100
|
-
const locationPath = agent.location === 'user'
|
|
3101
|
-
? `~/.claude/agents/${agent.agentType}.md`
|
|
3102
|
-
: agent.location === 'project'
|
|
3103
|
-
? `.claude/agents/${agent.agentType}.md`
|
|
3104
|
-
: '(built-in)'
|
|
3105
|
-
const displayModel = getDisplayModelName((agent as any).model || null)
|
|
3106
|
-
|
|
3107
|
-
const allowedTools = useMemo(() => {
|
|
3108
|
-
if (hasAllTools) return tools
|
|
3109
|
-
|
|
3110
|
-
return tools.filter(tool =>
|
|
3111
|
-
agentTools.some(allowedTool => {
|
|
3112
|
-
if (allowedTool.includes("*")) {
|
|
3113
|
-
const prefix = allowedTool.replace("*", "")
|
|
3114
|
-
return tool.name.startsWith(prefix)
|
|
3115
|
-
}
|
|
3116
|
-
return tool.name === allowedTool
|
|
3117
|
-
})
|
|
3118
|
-
)
|
|
3119
|
-
}, [tools, agentTools, hasAllTools])
|
|
3120
|
-
|
|
3121
|
-
return (
|
|
3122
|
-
<Box flexDirection="column">
|
|
3123
|
-
<Header title={`Agent: ${agent.agentType}`} subtitle="Details">
|
|
3124
|
-
<Box flexDirection="column" marginTop={1}>
|
|
3125
|
-
<Text><Text bold>Type:</Text> {agent.agentType}</Text>
|
|
3126
|
-
<Text><Text bold>Location:</Text> {agent.location} {locationPath !== '(built-in)' ? `· ${locationPath}` : ''}</Text>
|
|
3127
|
-
<Text><Text bold>Description:</Text> {agent.whenToUse}</Text>
|
|
3128
|
-
<Text><Text bold>Model:</Text> {displayModel}</Text>
|
|
3129
|
-
<Text><Text bold>Color:</Text> {agent.color || 'auto'}</Text>
|
|
3130
|
-
|
|
3131
|
-
<Box marginTop={1}>
|
|
3132
|
-
<Text bold>Tools:</Text>
|
|
3133
|
-
</Box>
|
|
3134
|
-
{hasAllTools ? (
|
|
3135
|
-
<Text color={theme.secondary}>All tools ({tools.length} available)</Text>
|
|
3136
|
-
) : (
|
|
3137
|
-
<Box flexDirection="column" paddingLeft={2}>
|
|
3138
|
-
{allowedTools.map(tool => (
|
|
3139
|
-
<Fragment key={tool.name}>
|
|
3140
|
-
<Text color={theme.secondary}>• {tool.name}</Text>
|
|
3141
|
-
</Fragment>
|
|
3142
|
-
))}
|
|
3143
|
-
</Box>
|
|
3144
|
-
)}
|
|
3145
|
-
|
|
3146
|
-
<Box marginTop={1}>
|
|
3147
|
-
<Text bold>System Prompt:</Text>
|
|
3148
|
-
</Box>
|
|
3149
|
-
<Box paddingLeft={2}>
|
|
3150
|
-
<Text>{agent.systemPrompt}</Text>
|
|
3151
|
-
</Box>
|
|
3152
|
-
</Box>
|
|
3153
|
-
</Header>
|
|
3154
|
-
<InstructionBar />
|
|
3155
|
-
</Box>
|
|
3156
|
-
)
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
// Edit agent component
|
|
3160
|
-
interface EditAgentProps {
|
|
3161
|
-
agent: AgentConfig
|
|
3162
|
-
tools: Tool[]
|
|
3163
|
-
setModeState: (state: ModeState) => void
|
|
3164
|
-
onAgentUpdated: (message: string) => void
|
|
3165
|
-
}
|
|
3166
|
-
|
|
3167
|
-
function EditAgent({ agent, tools, setModeState, onAgentUpdated }: EditAgentProps) {
|
|
3168
|
-
const theme = getTheme()
|
|
3169
|
-
const [currentStep, setCurrentStep] = useState<'description' | 'tools' | 'prompt' | 'confirm'>('description')
|
|
3170
|
-
const [isUpdating, setIsUpdating] = useState(false)
|
|
3171
|
-
|
|
3172
|
-
// 编辑状态
|
|
3173
|
-
const [editedDescription, setEditedDescription] = useState(agent.whenToUse)
|
|
3174
|
-
const [editedTools, setEditedTools] = useState<string[]>(
|
|
3175
|
-
Array.isArray(agent.tools) ? agent.tools : agent.tools === '*' ? ['*'] : []
|
|
3176
|
-
)
|
|
3177
|
-
const [editedPrompt, setEditedPrompt] = useState(agent.systemPrompt)
|
|
3178
|
-
const [error, setError] = useState<string | null>(null)
|
|
3179
|
-
|
|
3180
|
-
const handleSave = async () => {
|
|
3181
|
-
setIsUpdating(true)
|
|
3182
|
-
try {
|
|
3183
|
-
await updateAgent(agent, editedDescription, editedTools, editedPrompt, agent.color)
|
|
3184
|
-
clearAgentCache()
|
|
3185
|
-
onAgentUpdated(`Updated agent: ${agent.agentType}`)
|
|
3186
|
-
} catch (error) {
|
|
3187
|
-
setError((error as Error).message)
|
|
3188
|
-
setIsUpdating(false)
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
const renderStepContent = () => {
|
|
3193
|
-
switch (currentStep) {
|
|
3194
|
-
case 'description':
|
|
3195
|
-
return (
|
|
3196
|
-
<Box flexDirection="column">
|
|
3197
|
-
<Text bold>Edit Description:</Text>
|
|
3198
|
-
<Box marginTop={1}>
|
|
3199
|
-
<MultilineTextInput
|
|
3200
|
-
value={editedDescription}
|
|
3201
|
-
onChange={setEditedDescription}
|
|
3202
|
-
placeholder="Describe when to use this agent..."
|
|
3203
|
-
onSubmit={() => setCurrentStep('tools')}
|
|
3204
|
-
error={error}
|
|
3205
|
-
rows={4}
|
|
3206
|
-
/>
|
|
3207
|
-
</Box>
|
|
3208
|
-
</Box>
|
|
3209
|
-
)
|
|
3210
|
-
|
|
3211
|
-
case 'tools':
|
|
3212
|
-
return (
|
|
3213
|
-
<Box flexDirection="column">
|
|
3214
|
-
<Text bold>Edit Tools:</Text>
|
|
3215
|
-
<Box marginTop={1}>
|
|
3216
|
-
<ToolsStep
|
|
3217
|
-
createState={{
|
|
3218
|
-
selectedTools: editedTools,
|
|
3219
|
-
} as CreateState}
|
|
3220
|
-
setCreateState={(action) => {
|
|
3221
|
-
if (action.type === 'SET_SELECTED_TOOLS') {
|
|
3222
|
-
setEditedTools(action.value)
|
|
3223
|
-
setCurrentStep('prompt')
|
|
3224
|
-
}
|
|
3225
|
-
}}
|
|
3226
|
-
setModeState={() => {}}
|
|
3227
|
-
tools={tools}
|
|
3228
|
-
/>
|
|
3229
|
-
</Box>
|
|
3230
|
-
</Box>
|
|
3231
|
-
)
|
|
3232
|
-
|
|
3233
|
-
case 'prompt':
|
|
3234
|
-
return (
|
|
3235
|
-
<Box flexDirection="column">
|
|
3236
|
-
<Text bold>Edit System Prompt:</Text>
|
|
3237
|
-
<Box marginTop={1}>
|
|
3238
|
-
<MultilineTextInput
|
|
3239
|
-
value={editedPrompt}
|
|
3240
|
-
onChange={setEditedPrompt}
|
|
3241
|
-
placeholder="System prompt for the agent..."
|
|
3242
|
-
onSubmit={() => setCurrentStep('confirm')}
|
|
3243
|
-
error={error}
|
|
3244
|
-
rows={5}
|
|
3245
|
-
/>
|
|
3246
|
-
</Box>
|
|
3247
|
-
</Box>
|
|
3248
|
-
)
|
|
3249
|
-
|
|
3250
|
-
case 'confirm':
|
|
3251
|
-
const validation = validateAgentConfig({
|
|
3252
|
-
agentType: agent.agentType,
|
|
3253
|
-
whenToUse: editedDescription,
|
|
3254
|
-
systemPrompt: editedPrompt,
|
|
3255
|
-
selectedTools: editedTools
|
|
3256
|
-
})
|
|
3257
|
-
|
|
3258
|
-
return (
|
|
3259
|
-
<Box flexDirection="column">
|
|
3260
|
-
<Text bold>Confirm Changes:</Text>
|
|
3261
|
-
<Box flexDirection="column" marginTop={1}>
|
|
3262
|
-
<Text><Text bold>Agent:</Text> {agent.agentType}</Text>
|
|
3263
|
-
<Text><Text bold>Description:</Text> {editedDescription}</Text>
|
|
3264
|
-
<Text><Text bold>Tools:</Text> {editedTools.includes('*') ? 'All tools' : editedTools.join(', ')}</Text>
|
|
3265
|
-
<Text><Text bold>System Prompt:</Text> {editedPrompt.slice(0, 100)}{editedPrompt.length > 100 ? '...' : ''}</Text>
|
|
3266
|
-
|
|
3267
|
-
{validation.warnings.length > 0 && (
|
|
3268
|
-
<Box marginTop={1}>
|
|
3269
|
-
{validation.warnings.map((warning, idx) => (
|
|
3270
|
-
<Fragment key={idx}>
|
|
3271
|
-
<Text color={theme.warning}>⚠ {warning}</Text>
|
|
3272
|
-
</Fragment>
|
|
3273
|
-
))}
|
|
3274
|
-
</Box>
|
|
3275
|
-
)}
|
|
3276
|
-
|
|
3277
|
-
{error && (
|
|
3278
|
-
<Box marginTop={1}>
|
|
3279
|
-
<Text color={theme.error}>✗ {error}</Text>
|
|
3280
|
-
</Box>
|
|
3281
|
-
)}
|
|
3282
|
-
|
|
3283
|
-
<Box marginTop={2}>
|
|
3284
|
-
{isUpdating ? (
|
|
3285
|
-
<LoadingSpinner text="Updating agent..." />
|
|
3286
|
-
) : (
|
|
3287
|
-
<Text>Press Enter to save changes</Text>
|
|
3288
|
-
)}
|
|
3289
|
-
</Box>
|
|
3290
|
-
</Box>
|
|
3291
|
-
</Box>
|
|
3292
|
-
)
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
useInput((input, key) => {
|
|
3297
|
-
if (key.escape) {
|
|
3298
|
-
if (currentStep === 'description') {
|
|
3299
|
-
setModeState({ mode: "agent-menu", selectedAgent: agent })
|
|
3300
|
-
} else {
|
|
3301
|
-
// 返回上一步
|
|
3302
|
-
const steps: Array<typeof currentStep> = ['description', 'tools', 'prompt', 'confirm']
|
|
3303
|
-
const currentIndex = steps.indexOf(currentStep)
|
|
3304
|
-
if (currentIndex > 0) {
|
|
3305
|
-
setCurrentStep(steps[currentIndex - 1])
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
return
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
|
-
if (key.return && currentStep === 'confirm' && !isUpdating) {
|
|
3312
|
-
handleSave()
|
|
3313
|
-
}
|
|
3314
|
-
})
|
|
3315
|
-
|
|
3316
|
-
return (
|
|
3317
|
-
<Box flexDirection="column">
|
|
3318
|
-
<Header title={`Edit Agent: ${agent.agentType}`} subtitle={`Step ${['description', 'tools', 'prompt', 'confirm'].indexOf(currentStep) + 1}/4`}>
|
|
3319
|
-
<Box marginTop={1}>
|
|
3320
|
-
{renderStepContent()}
|
|
3321
|
-
</Box>
|
|
3322
|
-
</Header>
|
|
3323
|
-
<InstructionBar
|
|
3324
|
-
instructions={currentStep === 'confirm' ?
|
|
3325
|
-
"Press Enter to save · Esc to go back" :
|
|
3326
|
-
"Enter to continue · Esc to go back"
|
|
3327
|
-
}
|
|
3328
|
-
/>
|
|
3329
|
-
</Box>
|
|
3330
|
-
)
|
|
3331
|
-
}
|
|
3332
|
-
|
|
3333
|
-
// Delete confirmation
|
|
3334
|
-
interface DeleteConfirmProps {
|
|
3335
|
-
agent: AgentConfig
|
|
3336
|
-
setModeState: (state: ModeState) => void
|
|
3337
|
-
onAgentDeleted: (message: string) => void
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
function DeleteConfirm({ agent, setModeState, onAgentDeleted }: DeleteConfirmProps) {
|
|
3341
|
-
const [isDeleting, setIsDeleting] = useState(false)
|
|
3342
|
-
const [selected, setSelected] = useState(false) // false = No, true = Yes
|
|
3343
|
-
|
|
3344
|
-
const handleConfirm = async () => {
|
|
3345
|
-
if (selected) {
|
|
3346
|
-
setIsDeleting(true)
|
|
3347
|
-
try {
|
|
3348
|
-
await deleteAgent(agent)
|
|
3349
|
-
clearAgentCache()
|
|
3350
|
-
onAgentDeleted(`Deleted agent: ${agent.agentType}`)
|
|
3351
|
-
} catch (error) {
|
|
3352
|
-
console.error('Failed to delete agent:', error)
|
|
3353
|
-
setIsDeleting(false)
|
|
3354
|
-
// TODO: Show error to user
|
|
3355
|
-
}
|
|
3356
|
-
} else {
|
|
3357
|
-
setModeState({ mode: "agent-menu", selectedAgent: agent })
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
|
|
3361
|
-
useInput((input, key) => {
|
|
3362
|
-
if (key.return) {
|
|
3363
|
-
handleConfirm()
|
|
3364
|
-
} else if (key.leftArrow || key.rightArrow || key.tab) {
|
|
3365
|
-
setSelected(!selected)
|
|
3366
|
-
}
|
|
3367
|
-
})
|
|
3368
|
-
|
|
3369
|
-
if (isDeleting) {
|
|
3370
|
-
return (
|
|
3371
|
-
<Box flexDirection="column">
|
|
3372
|
-
<Header title="Delete agent" subtitle="Deleting...">
|
|
3373
|
-
<Box marginTop={1}>
|
|
3374
|
-
<LoadingSpinner text="Deleting agent..." />
|
|
3375
|
-
</Box>
|
|
3376
|
-
</Header>
|
|
3377
|
-
<InstructionBar />
|
|
3378
|
-
</Box>
|
|
3379
|
-
)
|
|
3380
|
-
}
|
|
3381
|
-
|
|
3382
|
-
return (
|
|
3383
|
-
<Box flexDirection="column">
|
|
3384
|
-
<Header title="Delete agent" subtitle={`Delete "${agent.agentType}"?`}>
|
|
3385
|
-
<Box marginTop={1}>
|
|
3386
|
-
<Text>This action cannot be undone. The agent file will be permanently deleted.</Text>
|
|
3387
|
-
<Box marginTop={2} gap={3}>
|
|
3388
|
-
<Text color={!selected ? 'cyan' : undefined}>
|
|
3389
|
-
{!selected ? `${UI_ICONS.pointer} ` : ' '}No
|
|
3390
|
-
</Text>
|
|
3391
|
-
<Text color={selected ? 'red' : undefined}>
|
|
3392
|
-
{selected ? `${UI_ICONS.pointer} ` : ' '}Yes, delete
|
|
3393
|
-
</Text>
|
|
3394
|
-
</Box>
|
|
3395
|
-
</Box>
|
|
3396
|
-
</Header>
|
|
3397
|
-
<InstructionBar />
|
|
3398
|
-
</Box>
|
|
3399
|
-
)
|
|
3400
|
-
}
|
|
3401
|
-
|
|
3402
|
-
export default {
|
|
3403
|
-
name: 'agents',
|
|
3404
|
-
description: 'Manage agent configurations',
|
|
3405
|
-
type: 'local-jsx' as const,
|
|
3406
|
-
isEnabled: true,
|
|
3407
|
-
isHidden: false,
|
|
3408
|
-
|
|
3409
|
-
async call(onExit: (message?: string) => void) {
|
|
3410
|
-
return <AgentsUI onExit={onExit} />
|
|
3411
|
-
},
|
|
3412
|
-
|
|
3413
|
-
userFacingName() {
|
|
3414
|
-
return 'agents'
|
|
3415
|
-
}
|
|
3416
|
-
}
|