@shareai-lab/kode 1.0.70 → 1.0.73
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/README.md +342 -75
- package/README.zh-CN.md +292 -0
- package/cli.js +62 -0
- package/package.json +49 -25
- package/scripts/postinstall.js +56 -0
- package/src/ProjectOnboarding.tsx +198 -0
- package/src/Tool.ts +82 -0
- package/src/commands/agents.tsx +3401 -0
- package/src/commands/approvedTools.ts +53 -0
- package/src/commands/bug.tsx +20 -0
- package/src/commands/clear.ts +43 -0
- package/src/commands/compact.ts +120 -0
- package/src/commands/config.tsx +19 -0
- package/src/commands/cost.ts +18 -0
- package/src/commands/ctx_viz.ts +209 -0
- package/src/commands/doctor.ts +24 -0
- package/src/commands/help.tsx +19 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/listen.ts +42 -0
- package/src/commands/login.tsx +51 -0
- package/src/commands/logout.tsx +40 -0
- package/src/commands/mcp.ts +41 -0
- package/src/commands/model.tsx +40 -0
- package/src/commands/modelstatus.tsx +20 -0
- package/src/commands/onboarding.tsx +34 -0
- package/src/commands/pr_comments.ts +59 -0
- package/src/commands/refreshCommands.ts +54 -0
- package/src/commands/release-notes.ts +34 -0
- package/src/commands/resume.tsx +31 -0
- package/src/commands/review.ts +49 -0
- package/src/commands/terminalSetup.ts +221 -0
- package/src/commands.ts +139 -0
- package/src/components/ApproveApiKey.tsx +93 -0
- package/src/components/AsciiLogo.tsx +13 -0
- package/src/components/AutoUpdater.tsx +148 -0
- package/src/components/Bug.tsx +367 -0
- package/src/components/Config.tsx +293 -0
- package/src/components/ConsoleOAuthFlow.tsx +327 -0
- package/src/components/Cost.tsx +23 -0
- package/src/components/CostThresholdDialog.tsx +46 -0
- package/src/components/CustomSelect/option-map.ts +42 -0
- package/src/components/CustomSelect/select-option.tsx +78 -0
- package/src/components/CustomSelect/select.tsx +152 -0
- package/src/components/CustomSelect/theme.ts +45 -0
- package/src/components/CustomSelect/use-select-state.ts +414 -0
- package/src/components/CustomSelect/use-select.ts +35 -0
- package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
- package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
- package/src/components/Help.tsx +215 -0
- package/src/components/HighlightedCode.tsx +33 -0
- package/src/components/InvalidConfigDialog.tsx +113 -0
- package/src/components/Link.tsx +32 -0
- package/src/components/LogSelector.tsx +86 -0
- package/src/components/Logo.tsx +145 -0
- package/src/components/MCPServerApprovalDialog.tsx +100 -0
- package/src/components/MCPServerDialogCopy.tsx +25 -0
- package/src/components/MCPServerMultiselectDialog.tsx +109 -0
- package/src/components/Message.tsx +221 -0
- package/src/components/MessageResponse.tsx +15 -0
- package/src/components/MessageSelector.tsx +211 -0
- package/src/components/ModeIndicator.tsx +88 -0
- package/src/components/ModelConfig.tsx +301 -0
- package/src/components/ModelListManager.tsx +227 -0
- package/src/components/ModelSelector.tsx +3386 -0
- package/src/components/ModelStatusDisplay.tsx +230 -0
- package/src/components/Onboarding.tsx +274 -0
- package/src/components/PressEnterToContinue.tsx +11 -0
- package/src/components/PromptInput.tsx +740 -0
- package/src/components/SentryErrorBoundary.ts +33 -0
- package/src/components/Spinner.tsx +129 -0
- package/src/components/StickerRequestForm.tsx +16 -0
- package/src/components/StructuredDiff.tsx +191 -0
- package/src/components/TextInput.tsx +259 -0
- package/src/components/TodoItem.tsx +11 -0
- package/src/components/TokenWarning.tsx +31 -0
- package/src/components/ToolUseLoader.tsx +40 -0
- package/src/components/TrustDialog.tsx +106 -0
- package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
- package/src/components/binary-feedback/utils.ts +220 -0
- package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +49 -0
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
- package/src/components/messages/AssistantTextMessage.tsx +144 -0
- package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
- package/src/components/messages/AssistantToolUseMessage.tsx +133 -0
- package/src/components/messages/TaskProgressMessage.tsx +32 -0
- package/src/components/messages/TaskToolMessage.tsx +58 -0
- package/src/components/messages/UserBashInputMessage.tsx +28 -0
- package/src/components/messages/UserCommandMessage.tsx +30 -0
- package/src/components/messages/UserKodingInputMessage.tsx +28 -0
- package/src/components/messages/UserPromptMessage.tsx +35 -0
- package/src/components/messages/UserTextMessage.tsx +39 -0
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
- package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +153 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +77 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +83 -0
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +240 -0
- package/src/components/permissions/PermissionRequest.tsx +101 -0
- package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
- package/src/components/permissions/hooks.ts +44 -0
- package/src/components/permissions/toolUseOptions.ts +59 -0
- package/src/components/permissions/utils.ts +23 -0
- package/src/constants/betas.ts +5 -0
- package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
- package/src/constants/figures.ts +4 -0
- package/src/constants/keys.ts +3 -0
- package/src/constants/macros.ts +8 -0
- package/src/constants/modelCapabilities.ts +179 -0
- package/src/constants/models.ts +1025 -0
- package/src/constants/oauth.ts +18 -0
- package/src/constants/product.ts +17 -0
- package/src/constants/prompts.ts +177 -0
- package/src/constants/releaseNotes.ts +7 -0
- package/src/context/PermissionContext.tsx +149 -0
- package/src/context.ts +278 -0
- package/src/cost-tracker.ts +84 -0
- package/src/entrypoints/cli.tsx +1518 -0
- package/src/entrypoints/mcp.ts +176 -0
- package/src/history.ts +25 -0
- package/src/hooks/useApiKeyVerification.ts +59 -0
- package/src/hooks/useArrowKeyHistory.ts +55 -0
- package/src/hooks/useCanUseTool.ts +138 -0
- package/src/hooks/useCancelRequest.ts +39 -0
- package/src/hooks/useDoublePress.ts +42 -0
- package/src/hooks/useExitOnCtrlCD.ts +31 -0
- package/src/hooks/useInterval.ts +25 -0
- package/src/hooks/useLogMessages.ts +16 -0
- package/src/hooks/useLogStartupTime.ts +12 -0
- package/src/hooks/useNotifyAfterTimeout.ts +65 -0
- package/src/hooks/usePermissionRequestLogging.ts +44 -0
- package/src/hooks/useTerminalSize.ts +49 -0
- package/src/hooks/useTextInput.ts +318 -0
- package/src/hooks/useUnifiedCompletion.ts +1404 -0
- package/src/messages.ts +38 -0
- package/src/permissions.ts +268 -0
- package/src/query.ts +707 -0
- package/src/screens/ConfigureNpmPrefix.tsx +197 -0
- package/src/screens/Doctor.tsx +219 -0
- package/src/screens/LogList.tsx +68 -0
- package/src/screens/REPL.tsx +798 -0
- package/src/screens/ResumeConversation.tsx +68 -0
- package/src/services/adapters/base.ts +38 -0
- package/src/services/adapters/chatCompletions.ts +90 -0
- package/src/services/adapters/responsesAPI.ts +170 -0
- package/src/services/browserMocks.ts +66 -0
- package/src/services/claude.ts +2083 -0
- package/src/services/customCommands.ts +704 -0
- package/src/services/fileFreshness.ts +377 -0
- package/src/services/gpt5ConnectionTest.ts +340 -0
- package/src/services/mcpClient.ts +564 -0
- package/src/services/mcpServerApproval.tsx +50 -0
- package/src/services/mentionProcessor.ts +273 -0
- package/src/services/modelAdapterFactory.ts +69 -0
- package/src/services/notifier.ts +40 -0
- package/src/services/oauth.ts +357 -0
- package/src/services/openai.ts +1305 -0
- package/src/services/responseStateManager.ts +90 -0
- package/src/services/sentry.ts +3 -0
- package/src/services/statsig.ts +171 -0
- package/src/services/statsigStorage.ts +86 -0
- package/src/services/systemReminder.ts +507 -0
- package/src/services/vcr.ts +161 -0
- package/src/test/testAdapters.ts +96 -0
- package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
- package/src/tools/ArchitectTool/prompt.ts +15 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +569 -0
- package/src/tools/BashTool/BashTool.tsx +243 -0
- package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
- package/src/tools/BashTool/OutputLine.tsx +49 -0
- package/src/tools/BashTool/prompt.ts +174 -0
- package/src/tools/BashTool/utils.ts +56 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +315 -0
- package/src/tools/FileEditTool/prompt.ts +51 -0
- package/src/tools/FileEditTool/utils.ts +58 -0
- package/src/tools/FileReadTool/FileReadTool.tsx +404 -0
- package/src/tools/FileReadTool/prompt.ts +7 -0
- package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
- package/src/tools/FileWriteTool/prompt.ts +10 -0
- package/src/tools/GlobTool/GlobTool.tsx +119 -0
- package/src/tools/GlobTool/prompt.ts +8 -0
- package/src/tools/GrepTool/GrepTool.tsx +147 -0
- package/src/tools/GrepTool/prompt.ts +11 -0
- package/src/tools/MCPTool/MCPTool.tsx +107 -0
- package/src/tools/MCPTool/prompt.ts +3 -0
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
- package/src/tools/MemoryReadTool/prompt.ts +3 -0
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
- package/src/tools/MemoryWriteTool/prompt.ts +3 -0
- package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
- package/src/tools/MultiEditTool/prompt.ts +45 -0
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
- package/src/tools/NotebookEditTool/prompt.ts +3 -0
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +258 -0
- package/src/tools/NotebookReadTool/prompt.ts +3 -0
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
- package/src/tools/StickerRequestTool/prompt.ts +19 -0
- package/src/tools/TaskTool/TaskTool.tsx +466 -0
- package/src/tools/TaskTool/constants.ts +1 -0
- package/src/tools/TaskTool/prompt.ts +92 -0
- package/src/tools/ThinkTool/ThinkTool.tsx +54 -0
- package/src/tools/ThinkTool/prompt.ts +12 -0
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +290 -0
- package/src/tools/TodoWriteTool/prompt.ts +63 -0
- package/src/tools/lsTool/lsTool.tsx +272 -0
- package/src/tools/lsTool/prompt.ts +2 -0
- package/src/tools.ts +63 -0
- package/src/types/PermissionMode.ts +120 -0
- package/src/types/RequestContext.ts +72 -0
- package/src/types/conversation.ts +51 -0
- package/src/types/logs.ts +58 -0
- package/src/types/modelCapabilities.ts +64 -0
- package/src/types/notebook.ts +87 -0
- package/src/utils/Cursor.ts +436 -0
- package/src/utils/PersistentShell.ts +373 -0
- package/src/utils/advancedFuzzyMatcher.ts +290 -0
- package/src/utils/agentLoader.ts +284 -0
- package/src/utils/agentStorage.ts +97 -0
- package/src/utils/array.ts +3 -0
- package/src/utils/ask.tsx +99 -0
- package/src/utils/auth.ts +13 -0
- package/src/utils/autoCompactCore.ts +223 -0
- package/src/utils/autoUpdater.ts +318 -0
- package/src/utils/betas.ts +20 -0
- package/src/utils/browser.ts +14 -0
- package/src/utils/cleanup.ts +72 -0
- package/src/utils/commands.ts +261 -0
- package/src/utils/commonUnixCommands.ts +161 -0
- package/src/utils/config.ts +942 -0
- package/src/utils/conversationRecovery.ts +55 -0
- package/src/utils/debugLogger.ts +1123 -0
- package/src/utils/diff.ts +42 -0
- package/src/utils/env.ts +57 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/exampleCommands.ts +109 -0
- package/src/utils/execFileNoThrow.ts +51 -0
- package/src/utils/expertChatStorage.ts +136 -0
- package/src/utils/file.ts +402 -0
- package/src/utils/fileRecoveryCore.ts +71 -0
- package/src/utils/format.tsx +44 -0
- package/src/utils/fuzzyMatcher.ts +328 -0
- package/src/utils/generators.ts +62 -0
- package/src/utils/git.ts +92 -0
- package/src/utils/globalLogger.ts +77 -0
- package/src/utils/http.ts +10 -0
- package/src/utils/imagePaste.ts +38 -0
- package/src/utils/json.ts +13 -0
- package/src/utils/log.ts +382 -0
- package/src/utils/markdown.ts +213 -0
- package/src/utils/messageContextManager.ts +289 -0
- package/src/utils/messages.tsx +939 -0
- package/src/utils/model.ts +836 -0
- package/src/utils/permissions/filesystem.ts +118 -0
- package/src/utils/responseState.ts +23 -0
- package/src/utils/ripgrep.ts +167 -0
- package/src/utils/secureFile.ts +559 -0
- package/src/utils/sessionState.ts +49 -0
- package/src/utils/state.ts +25 -0
- package/src/utils/style.ts +29 -0
- package/src/utils/terminal.ts +50 -0
- package/src/utils/theme.ts +133 -0
- package/src/utils/thinking.ts +144 -0
- package/src/utils/todoStorage.ts +431 -0
- package/src/utils/tokens.ts +43 -0
- package/src/utils/toolExecutionController.ts +163 -0
- package/src/utils/unaryLogging.ts +26 -0
- package/src/utils/user.ts +37 -0
- package/src/utils/validate.ts +165 -0
- package/cli.mjs +0 -1803
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getGlobalConfig } from './config'
|
|
2
|
+
|
|
3
|
+
export interface Theme {
|
|
4
|
+
bashBorder: string
|
|
5
|
+
claude: string
|
|
6
|
+
koding: string
|
|
7
|
+
permission: string
|
|
8
|
+
secondaryBorder: string
|
|
9
|
+
text: string
|
|
10
|
+
secondaryText: string
|
|
11
|
+
suggestion: string
|
|
12
|
+
// Semantic colors
|
|
13
|
+
success: string
|
|
14
|
+
error: string
|
|
15
|
+
warning: string
|
|
16
|
+
// UI colors
|
|
17
|
+
primary: string
|
|
18
|
+
secondary: string
|
|
19
|
+
diff: {
|
|
20
|
+
added: string
|
|
21
|
+
removed: string
|
|
22
|
+
addedDimmed: string
|
|
23
|
+
removedDimmed: string
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const lightTheme: Theme = {
|
|
28
|
+
bashBorder: '#ff0087',
|
|
29
|
+
claude: '#7aff59ff',
|
|
30
|
+
koding: '#9dff00ff',
|
|
31
|
+
permission: '#e9c61aff',
|
|
32
|
+
secondaryBorder: '#999',
|
|
33
|
+
text: '#000',
|
|
34
|
+
secondaryText: '#666',
|
|
35
|
+
suggestion: '#32e98aff',
|
|
36
|
+
success: '#2c7a39',
|
|
37
|
+
error: '#ab2b3f',
|
|
38
|
+
warning: '#966c1e',
|
|
39
|
+
primary: '#000',
|
|
40
|
+
secondary: '#666',
|
|
41
|
+
diff: {
|
|
42
|
+
added: '#69db7c',
|
|
43
|
+
removed: '#ffa8b4',
|
|
44
|
+
addedDimmed: '#c7e1cb',
|
|
45
|
+
removedDimmed: '#fdd2d8',
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lightDaltonizedTheme: Theme = {
|
|
50
|
+
bashBorder: '#0066cc', // Blue instead of pink for better contrast
|
|
51
|
+
claude: '#5f97cd', // Orange adjusted for deuteranopia
|
|
52
|
+
koding: '#0000ff',
|
|
53
|
+
permission: '#3366ff', // Brighter blue for better visibility
|
|
54
|
+
secondaryBorder: '#999',
|
|
55
|
+
text: '#000',
|
|
56
|
+
secondaryText: '#666',
|
|
57
|
+
suggestion: '#3366ff',
|
|
58
|
+
success: '#006699', // Blue instead of green
|
|
59
|
+
error: '#cc0000', // Pure red for better distinction
|
|
60
|
+
warning: '#ff9900', // Orange adjusted for deuteranopia
|
|
61
|
+
primary: '#000',
|
|
62
|
+
secondary: '#666',
|
|
63
|
+
diff: {
|
|
64
|
+
added: '#99ccff', // Light blue instead of green
|
|
65
|
+
removed: '#ffcccc', // Light red for better contrast
|
|
66
|
+
addedDimmed: '#d1e7fd',
|
|
67
|
+
removedDimmed: '#ffe9e9',
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const darkTheme: Theme = {
|
|
72
|
+
bashBorder: '#fd5db1',
|
|
73
|
+
claude: '#5f97cd',
|
|
74
|
+
koding: '#0000ff',
|
|
75
|
+
permission: '#b1b9f9',
|
|
76
|
+
secondaryBorder: '#888',
|
|
77
|
+
text: '#fff',
|
|
78
|
+
secondaryText: '#999',
|
|
79
|
+
suggestion: '#b1b9f9',
|
|
80
|
+
success: '#4eba65',
|
|
81
|
+
error: '#ff6b80',
|
|
82
|
+
warning: '#ffc107',
|
|
83
|
+
primary: '#fff',
|
|
84
|
+
secondary: '#999',
|
|
85
|
+
diff: {
|
|
86
|
+
added: '#225c2b',
|
|
87
|
+
removed: '#7a2936',
|
|
88
|
+
addedDimmed: '#47584a',
|
|
89
|
+
removedDimmed: '#69484d',
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const darkDaltonizedTheme: Theme = {
|
|
94
|
+
bashBorder: '#3399ff', // Bright blue instead of pink
|
|
95
|
+
claude: '#5f97cd', // Orange adjusted for deuteranopia
|
|
96
|
+
koding: '#0000ff',
|
|
97
|
+
permission: '#99ccff', // Light blue for better contrast
|
|
98
|
+
secondaryBorder: '#888',
|
|
99
|
+
text: '#fff',
|
|
100
|
+
secondaryText: '#999',
|
|
101
|
+
suggestion: '#99ccff',
|
|
102
|
+
success: '#3399ff', // Bright blue instead of green
|
|
103
|
+
error: '#ff6666', // Bright red for better visibility
|
|
104
|
+
warning: '#ffcc00', // Yellow-orange for deuteranopia
|
|
105
|
+
primary: '#fff',
|
|
106
|
+
secondary: '#999',
|
|
107
|
+
diff: {
|
|
108
|
+
added: '#004466', // Dark blue instead of green
|
|
109
|
+
removed: '#660000', // Dark red for better contrast
|
|
110
|
+
addedDimmed: '#3e515b',
|
|
111
|
+
removedDimmed: '#3e2c2c',
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type ThemeNames =
|
|
116
|
+
| 'dark'
|
|
117
|
+
| 'light'
|
|
118
|
+
| 'light-daltonized'
|
|
119
|
+
| 'dark-daltonized'
|
|
120
|
+
|
|
121
|
+
export function getTheme(overrideTheme?: ThemeNames): Theme {
|
|
122
|
+
const config = getGlobalConfig()
|
|
123
|
+
switch (overrideTheme ?? config.theme) {
|
|
124
|
+
case 'light':
|
|
125
|
+
return lightTheme
|
|
126
|
+
case 'light-daltonized':
|
|
127
|
+
return lightDaltonizedTheme
|
|
128
|
+
case 'dark-daltonized':
|
|
129
|
+
return darkDaltonizedTheme
|
|
130
|
+
default:
|
|
131
|
+
return darkTheme
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { last } from 'lodash-es'
|
|
2
|
+
import type { Message } from '../query'
|
|
3
|
+
import { logEvent } from '../services/statsig'
|
|
4
|
+
import { getLastAssistantMessageId } from './messages'
|
|
5
|
+
import { ThinkTool } from '../tools/ThinkTool/ThinkTool'
|
|
6
|
+
import { USE_BEDROCK, USE_VERTEX, getModelManager } from './model'
|
|
7
|
+
import { getGlobalConfig } from './config'
|
|
8
|
+
|
|
9
|
+
export async function getMaxThinkingTokens(
|
|
10
|
+
messages: Message[],
|
|
11
|
+
): Promise<number> {
|
|
12
|
+
if (process.env.MAX_THINKING_TOKENS) {
|
|
13
|
+
const tokens = parseInt(process.env.MAX_THINKING_TOKENS, 10)
|
|
14
|
+
logEvent('tengu_thinking', {
|
|
15
|
+
method: 'scratchpad',
|
|
16
|
+
tokenCount: tokens.toString(),
|
|
17
|
+
messageId: getLastAssistantMessageId(messages),
|
|
18
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
19
|
+
})
|
|
20
|
+
return tokens
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (await ThinkTool.isEnabled()) {
|
|
24
|
+
logEvent('tengu_thinking', {
|
|
25
|
+
method: 'scratchpad',
|
|
26
|
+
tokenCount: '0',
|
|
27
|
+
messageId: getLastAssistantMessageId(messages),
|
|
28
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
29
|
+
})
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const lastMessage = last(messages)
|
|
34
|
+
if (
|
|
35
|
+
lastMessage?.type !== 'user' ||
|
|
36
|
+
typeof lastMessage.message.content !== 'string'
|
|
37
|
+
) {
|
|
38
|
+
logEvent('tengu_thinking', {
|
|
39
|
+
method: 'scratchpad',
|
|
40
|
+
tokenCount: '0',
|
|
41
|
+
messageId: getLastAssistantMessageId(messages),
|
|
42
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
43
|
+
})
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = lastMessage.message.content.toLowerCase()
|
|
48
|
+
if (
|
|
49
|
+
content.includes('think harder') ||
|
|
50
|
+
content.includes('think intensely') ||
|
|
51
|
+
content.includes('think longer') ||
|
|
52
|
+
content.includes('think really hard') ||
|
|
53
|
+
content.includes('think super hard') ||
|
|
54
|
+
content.includes('think very hard') ||
|
|
55
|
+
content.includes('ultrathink')
|
|
56
|
+
) {
|
|
57
|
+
logEvent('tengu_thinking', {
|
|
58
|
+
method: 'scratchpad',
|
|
59
|
+
tokenCount: '31999',
|
|
60
|
+
messageId: getLastAssistantMessageId(messages),
|
|
61
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
62
|
+
})
|
|
63
|
+
return 32_000 - 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
content.includes('think about it') ||
|
|
68
|
+
content.includes('think a lot') ||
|
|
69
|
+
content.includes('think hard') ||
|
|
70
|
+
content.includes('think more') ||
|
|
71
|
+
content.includes('megathink')
|
|
72
|
+
) {
|
|
73
|
+
logEvent('tengu_thinking', {
|
|
74
|
+
method: 'scratchpad',
|
|
75
|
+
tokenCount: '10000',
|
|
76
|
+
messageId: getLastAssistantMessageId(messages),
|
|
77
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
78
|
+
})
|
|
79
|
+
return 10_000
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (content.includes('think')) {
|
|
83
|
+
logEvent('tengu_thinking', {
|
|
84
|
+
method: 'scratchpad',
|
|
85
|
+
tokenCount: '4000',
|
|
86
|
+
messageId: getLastAssistantMessageId(messages),
|
|
87
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
88
|
+
})
|
|
89
|
+
return 4_000
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logEvent('tengu_thinking', {
|
|
93
|
+
method: 'scratchpad',
|
|
94
|
+
tokenCount: '0',
|
|
95
|
+
messageId: getLastAssistantMessageId(messages),
|
|
96
|
+
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
|
97
|
+
})
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function getReasoningEffort(
|
|
102
|
+
modelProfile: any,
|
|
103
|
+
messages: Message[],
|
|
104
|
+
): Promise<'low' | 'medium' | 'high' | null> {
|
|
105
|
+
const thinkingTokens = await getMaxThinkingTokens(messages)
|
|
106
|
+
|
|
107
|
+
// Get reasoning effort from ModelProfile first, then fallback to config
|
|
108
|
+
let reasoningEffort: 'low' | 'medium' | 'high' | undefined
|
|
109
|
+
if (modelProfile?.reasoningEffort) {
|
|
110
|
+
reasoningEffort = modelProfile.reasoningEffort
|
|
111
|
+
} else {
|
|
112
|
+
// 🔧 Fix: Use ModelManager fallback instead of legacy config
|
|
113
|
+
const modelManager = getModelManager()
|
|
114
|
+
const fallbackProfile = modelManager.getModel('main')
|
|
115
|
+
reasoningEffort = fallbackProfile?.reasoningEffort || 'medium'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const maxEffort =
|
|
119
|
+
reasoningEffort === 'high'
|
|
120
|
+
? 2
|
|
121
|
+
: reasoningEffort === 'medium'
|
|
122
|
+
? 1
|
|
123
|
+
: reasoningEffort === 'low'
|
|
124
|
+
? 0
|
|
125
|
+
: null
|
|
126
|
+
if (!maxEffort) {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let effort = 0
|
|
131
|
+
if (thinkingTokens < 10_000) {
|
|
132
|
+
effort = 0
|
|
133
|
+
} else if (thinkingTokens >= 10_000 && thinkingTokens < 30_000) {
|
|
134
|
+
effort = 1
|
|
135
|
+
} else {
|
|
136
|
+
effort = 2
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (effort > maxEffort) {
|
|
140
|
+
return maxEffort === 2 ? 'high' : maxEffort === 1 ? 'medium' : 'low'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return effort === 2 ? 'high' : effort === 1 ? 'medium' : 'low'
|
|
144
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { setSessionState, getSessionState } from './sessionState'
|
|
2
|
+
import { readAgentData, writeAgentData, resolveAgentId } from './agentStorage'
|
|
3
|
+
|
|
4
|
+
export interface TodoItem {
|
|
5
|
+
id: string
|
|
6
|
+
content: string
|
|
7
|
+
status: 'pending' | 'in_progress' | 'completed'
|
|
8
|
+
priority: 'high' | 'medium' | 'low'
|
|
9
|
+
createdAt?: number
|
|
10
|
+
updatedAt?: number
|
|
11
|
+
tags?: string[]
|
|
12
|
+
estimatedHours?: number
|
|
13
|
+
previousStatus?: 'pending' | 'in_progress' | 'completed'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TodoQuery {
|
|
17
|
+
status?: TodoItem['status'][]
|
|
18
|
+
priority?: TodoItem['priority'][]
|
|
19
|
+
contentMatch?: string
|
|
20
|
+
tags?: string[]
|
|
21
|
+
dateRange?: { from?: Date; to?: Date }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TodoStorageConfig {
|
|
25
|
+
maxTodos: number
|
|
26
|
+
autoArchiveCompleted: boolean
|
|
27
|
+
sortBy: 'createdAt' | 'updatedAt' | 'priority' | 'status'
|
|
28
|
+
sortOrder: 'asc' | 'desc'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TODO_STORAGE_KEY = 'todos'
|
|
32
|
+
const TODO_CONFIG_KEY = 'todoConfig'
|
|
33
|
+
const TODO_CACHE_KEY = 'todoCache'
|
|
34
|
+
|
|
35
|
+
// Default configuration
|
|
36
|
+
const DEFAULT_CONFIG: TodoStorageConfig = {
|
|
37
|
+
maxTodos: 100,
|
|
38
|
+
autoArchiveCompleted: false,
|
|
39
|
+
sortBy: 'status', // Using smart sorting now
|
|
40
|
+
sortOrder: 'desc',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// In-memory cache for performance
|
|
44
|
+
let todoCache: TodoItem[] | null = null
|
|
45
|
+
let cacheTimestamp = 0
|
|
46
|
+
const CACHE_TTL = 5000 // 5 seconds cache
|
|
47
|
+
|
|
48
|
+
// Performance metrics
|
|
49
|
+
export interface TodoMetrics {
|
|
50
|
+
totalOperations: number
|
|
51
|
+
cacheHits: number
|
|
52
|
+
cacheMisses: number
|
|
53
|
+
lastOperation: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function invalidateCache(): void {
|
|
57
|
+
todoCache = null
|
|
58
|
+
cacheTimestamp = 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function updateMetrics(operation: string, cacheHit: boolean = false): void {
|
|
62
|
+
const sessionState = getSessionState() as any
|
|
63
|
+
const metrics = sessionState.todoMetrics || {
|
|
64
|
+
totalOperations: 0,
|
|
65
|
+
cacheHits: 0,
|
|
66
|
+
cacheMisses: 0,
|
|
67
|
+
lastOperation: 0,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
metrics.totalOperations++
|
|
71
|
+
metrics.lastOperation = Date.now()
|
|
72
|
+
|
|
73
|
+
if (cacheHit) {
|
|
74
|
+
metrics.cacheHits++
|
|
75
|
+
} else {
|
|
76
|
+
metrics.cacheMisses++
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setSessionState({
|
|
80
|
+
...sessionState,
|
|
81
|
+
todoMetrics: metrics,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getTodoMetrics(): TodoMetrics {
|
|
86
|
+
const sessionState = getSessionState() as any
|
|
87
|
+
return (
|
|
88
|
+
sessionState.todoMetrics || {
|
|
89
|
+
totalOperations: 0,
|
|
90
|
+
cacheHits: 0,
|
|
91
|
+
cacheMisses: 0,
|
|
92
|
+
lastOperation: 0,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getTodos(agentId?: string): TodoItem[] {
|
|
98
|
+
const resolvedAgentId = resolveAgentId(agentId)
|
|
99
|
+
const now = Date.now()
|
|
100
|
+
|
|
101
|
+
// For agent-scoped storage, use file-based storage instead of session state
|
|
102
|
+
if (agentId) {
|
|
103
|
+
updateMetrics('getTodos', false)
|
|
104
|
+
const agentTodos = readAgentData<TodoItem[]>(resolvedAgentId) || []
|
|
105
|
+
|
|
106
|
+
// Update cache with agent-specific cache key
|
|
107
|
+
const agentCacheKey = `todoCache_${resolvedAgentId}`
|
|
108
|
+
// Note: In production, we'd want agent-specific caching
|
|
109
|
+
|
|
110
|
+
return agentTodos
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Original session-based storage for backward compatibility
|
|
114
|
+
// Check cache first
|
|
115
|
+
if (todoCache && now - cacheTimestamp < CACHE_TTL) {
|
|
116
|
+
updateMetrics('getTodos', true)
|
|
117
|
+
return todoCache
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
updateMetrics('getTodos', false)
|
|
121
|
+
const sessionState = getSessionState()
|
|
122
|
+
const todos = (sessionState as any)[TODO_STORAGE_KEY] || []
|
|
123
|
+
|
|
124
|
+
// Update cache
|
|
125
|
+
todoCache = [...todos]
|
|
126
|
+
cacheTimestamp = now
|
|
127
|
+
|
|
128
|
+
return todos
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function setTodos(todos: TodoItem[], agentId?: string): void {
|
|
132
|
+
const resolvedAgentId = resolveAgentId(agentId)
|
|
133
|
+
const config = getTodoConfig()
|
|
134
|
+
const existingTodos = getTodos(agentId)
|
|
135
|
+
|
|
136
|
+
// For agent-scoped storage, use file-based storage
|
|
137
|
+
if (agentId) {
|
|
138
|
+
// Validate todo limit
|
|
139
|
+
if (todos.length > config.maxTodos) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Todo limit exceeded. Maximum ${config.maxTodos} todos allowed.`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Auto-archive completed todos if enabled
|
|
146
|
+
let processedTodos = todos
|
|
147
|
+
if (config.autoArchiveCompleted) {
|
|
148
|
+
processedTodos = todos.filter(todo => todo.status !== 'completed')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const updatedTodos = processedTodos.map(todo => {
|
|
152
|
+
// Find existing todo to track status changes
|
|
153
|
+
const existingTodo = existingTodos.find(
|
|
154
|
+
existing => existing.id === todo.id,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
...todo,
|
|
159
|
+
updatedAt: Date.now(),
|
|
160
|
+
createdAt: todo.createdAt || Date.now(),
|
|
161
|
+
previousStatus:
|
|
162
|
+
existingTodo?.status !== todo.status
|
|
163
|
+
? existingTodo?.status
|
|
164
|
+
: todo.previousStatus,
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Smart sorting for agent todos
|
|
169
|
+
updatedTodos.sort((a, b) => {
|
|
170
|
+
// 1. Status priority: in_progress > pending > completed
|
|
171
|
+
const statusOrder = { in_progress: 3, pending: 2, completed: 1 }
|
|
172
|
+
const statusDiff = statusOrder[b.status] - statusOrder[a.status]
|
|
173
|
+
if (statusDiff !== 0) return statusDiff
|
|
174
|
+
|
|
175
|
+
// 2. For same status, sort by priority: high > medium > low
|
|
176
|
+
const priorityOrder = { high: 3, medium: 2, low: 1 }
|
|
177
|
+
const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority]
|
|
178
|
+
if (priorityDiff !== 0) return priorityDiff
|
|
179
|
+
|
|
180
|
+
// 3. For same status and priority, sort by updatedAt (newest first)
|
|
181
|
+
const aTime = a.updatedAt || 0
|
|
182
|
+
const bTime = b.updatedAt || 0
|
|
183
|
+
return bTime - aTime
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// Write to agent-specific storage
|
|
187
|
+
writeAgentData(resolvedAgentId, updatedTodos)
|
|
188
|
+
updateMetrics('setTodos')
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Original session-based logic for backward compatibility
|
|
193
|
+
// Validate todo limit
|
|
194
|
+
if (todos.length > config.maxTodos) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Todo limit exceeded. Maximum ${config.maxTodos} todos allowed.`,
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Auto-archive completed todos if enabled
|
|
201
|
+
let processedTodos = todos
|
|
202
|
+
if (config.autoArchiveCompleted) {
|
|
203
|
+
processedTodos = todos.filter(todo => todo.status !== 'completed')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const updatedTodos = processedTodos.map(todo => {
|
|
207
|
+
// Find existing todo to track status changes
|
|
208
|
+
const existingTodo = existingTodos.find(existing => existing.id === todo.id)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
...todo,
|
|
212
|
+
updatedAt: Date.now(),
|
|
213
|
+
createdAt: todo.createdAt || Date.now(),
|
|
214
|
+
previousStatus:
|
|
215
|
+
existingTodo?.status !== todo.status
|
|
216
|
+
? existingTodo?.status
|
|
217
|
+
: todo.previousStatus,
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Smart sorting: status -> priority -> updatedAt
|
|
222
|
+
updatedTodos.sort((a, b) => {
|
|
223
|
+
// 1. Status priority: in_progress > pending > completed
|
|
224
|
+
const statusOrder = { in_progress: 3, pending: 2, completed: 1 }
|
|
225
|
+
const statusDiff = statusOrder[b.status] - statusOrder[a.status]
|
|
226
|
+
if (statusDiff !== 0) return statusDiff
|
|
227
|
+
|
|
228
|
+
// 2. For same status, sort by priority: high > medium > low
|
|
229
|
+
const priorityOrder = { high: 3, medium: 2, low: 1 }
|
|
230
|
+
const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority]
|
|
231
|
+
if (priorityDiff !== 0) return priorityDiff
|
|
232
|
+
|
|
233
|
+
// 3. For same status and priority, sort by updatedAt (newest first)
|
|
234
|
+
const aTime = a.updatedAt || 0
|
|
235
|
+
const bTime = b.updatedAt || 0
|
|
236
|
+
return bTime - aTime
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
setSessionState({
|
|
240
|
+
...getSessionState(),
|
|
241
|
+
[TODO_STORAGE_KEY]: updatedTodos,
|
|
242
|
+
} as any)
|
|
243
|
+
|
|
244
|
+
// Invalidate cache
|
|
245
|
+
invalidateCache()
|
|
246
|
+
updateMetrics('setTodos')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getTodoConfig(): TodoStorageConfig {
|
|
250
|
+
const sessionState = getSessionState() as any
|
|
251
|
+
return { ...DEFAULT_CONFIG, ...(sessionState[TODO_CONFIG_KEY] || {}) }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function setTodoConfig(config: Partial<TodoStorageConfig>): void {
|
|
255
|
+
const currentConfig = getTodoConfig()
|
|
256
|
+
const newConfig = { ...currentConfig, ...config }
|
|
257
|
+
|
|
258
|
+
setSessionState({
|
|
259
|
+
...getSessionState(),
|
|
260
|
+
[TODO_CONFIG_KEY]: newConfig,
|
|
261
|
+
} as any)
|
|
262
|
+
|
|
263
|
+
// Re-sort existing todos if sort order changed
|
|
264
|
+
if (config.sortBy || config.sortOrder) {
|
|
265
|
+
const todos = getTodos()
|
|
266
|
+
setTodos(todos) // This will re-sort according to new config
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function addTodo(
|
|
271
|
+
todo: Omit<TodoItem, 'createdAt' | 'updatedAt'>,
|
|
272
|
+
): TodoItem[] {
|
|
273
|
+
const todos = getTodos()
|
|
274
|
+
|
|
275
|
+
// Check for duplicate IDs
|
|
276
|
+
if (todos.some(existing => existing.id === todo.id)) {
|
|
277
|
+
throw new Error(`Todo with ID '${todo.id}' already exists`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const newTodo: TodoItem = {
|
|
281
|
+
...todo,
|
|
282
|
+
createdAt: Date.now(),
|
|
283
|
+
updatedAt: Date.now(),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const updatedTodos = [...todos, newTodo]
|
|
287
|
+
setTodos(updatedTodos)
|
|
288
|
+
updateMetrics('addTodo')
|
|
289
|
+
return updatedTodos
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function updateTodo(id: string, updates: Partial<TodoItem>): TodoItem[] {
|
|
293
|
+
const todos = getTodos()
|
|
294
|
+
const existingTodo = todos.find(todo => todo.id === id)
|
|
295
|
+
|
|
296
|
+
if (!existingTodo) {
|
|
297
|
+
throw new Error(`Todo with ID '${id}' not found`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const updatedTodos = todos.map(todo =>
|
|
301
|
+
todo.id === id ? { ...todo, ...updates, updatedAt: Date.now() } : todo,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
setTodos(updatedTodos)
|
|
305
|
+
updateMetrics('updateTodo')
|
|
306
|
+
return updatedTodos
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function deleteTodo(id: string): TodoItem[] {
|
|
310
|
+
const todos = getTodos()
|
|
311
|
+
const todoExists = todos.some(todo => todo.id === id)
|
|
312
|
+
|
|
313
|
+
if (!todoExists) {
|
|
314
|
+
throw new Error(`Todo with ID '${id}' not found`)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const updatedTodos = todos.filter(todo => todo.id !== id)
|
|
318
|
+
setTodos(updatedTodos)
|
|
319
|
+
updateMetrics('deleteTodo')
|
|
320
|
+
return updatedTodos
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function clearTodos(): void {
|
|
324
|
+
setTodos([])
|
|
325
|
+
updateMetrics('clearTodos')
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function getTodoById(id: string): TodoItem | undefined {
|
|
329
|
+
const todos = getTodos()
|
|
330
|
+
updateMetrics('getTodoById')
|
|
331
|
+
return todos.find(todo => todo.id === id)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function getTodosByStatus(status: TodoItem['status']): TodoItem[] {
|
|
335
|
+
const todos = getTodos()
|
|
336
|
+
updateMetrics('getTodosByStatus')
|
|
337
|
+
return todos.filter(todo => todo.status === status)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function getTodosByPriority(priority: TodoItem['priority']): TodoItem[] {
|
|
341
|
+
const todos = getTodos()
|
|
342
|
+
updateMetrics('getTodosByPriority')
|
|
343
|
+
return todos.filter(todo => todo.priority === priority)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Advanced query function
|
|
347
|
+
export function queryTodos(query: TodoQuery): TodoItem[] {
|
|
348
|
+
const todos = getTodos()
|
|
349
|
+
updateMetrics('queryTodos')
|
|
350
|
+
|
|
351
|
+
return todos.filter(todo => {
|
|
352
|
+
// Status filter
|
|
353
|
+
if (query.status && !query.status.includes(todo.status)) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Priority filter
|
|
358
|
+
if (query.priority && !query.priority.includes(todo.priority)) {
|
|
359
|
+
return false
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Content search
|
|
363
|
+
if (
|
|
364
|
+
query.contentMatch &&
|
|
365
|
+
!todo.content.toLowerCase().includes(query.contentMatch.toLowerCase())
|
|
366
|
+
) {
|
|
367
|
+
return false
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Tags filter
|
|
371
|
+
if (query.tags && todo.tags) {
|
|
372
|
+
const hasMatchingTag = query.tags.some(tag => todo.tags!.includes(tag))
|
|
373
|
+
if (!hasMatchingTag) return false
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Date range filter
|
|
377
|
+
if (query.dateRange) {
|
|
378
|
+
const todoDate = new Date(todo.createdAt || 0)
|
|
379
|
+
if (query.dateRange.from && todoDate < query.dateRange.from) return false
|
|
380
|
+
if (query.dateRange.to && todoDate > query.dateRange.to) return false
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return true
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Utility functions
|
|
388
|
+
export function getTodoStatistics() {
|
|
389
|
+
const todos = getTodos()
|
|
390
|
+
const metrics = getTodoMetrics()
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
total: todos.length,
|
|
394
|
+
byStatus: {
|
|
395
|
+
pending: todos.filter(t => t.status === 'pending').length,
|
|
396
|
+
in_progress: todos.filter(t => t.status === 'in_progress').length,
|
|
397
|
+
completed: todos.filter(t => t.status === 'completed').length,
|
|
398
|
+
},
|
|
399
|
+
byPriority: {
|
|
400
|
+
high: todos.filter(t => t.priority === 'high').length,
|
|
401
|
+
medium: todos.filter(t => t.priority === 'medium').length,
|
|
402
|
+
low: todos.filter(t => t.priority === 'low').length,
|
|
403
|
+
},
|
|
404
|
+
metrics,
|
|
405
|
+
cacheEfficiency:
|
|
406
|
+
metrics.totalOperations > 0
|
|
407
|
+
? Math.round((metrics.cacheHits / metrics.totalOperations) * 100)
|
|
408
|
+
: 0,
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function optimizeTodoStorage(): void {
|
|
413
|
+
// Force cache refresh
|
|
414
|
+
invalidateCache()
|
|
415
|
+
|
|
416
|
+
// Compact storage by removing any invalid entries
|
|
417
|
+
const todos = getTodos()
|
|
418
|
+
const validTodos = todos.filter(
|
|
419
|
+
todo =>
|
|
420
|
+
todo.id &&
|
|
421
|
+
todo.content &&
|
|
422
|
+
['pending', 'in_progress', 'completed'].includes(todo.status) &&
|
|
423
|
+
['high', 'medium', 'low'].includes(todo.priority),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if (validTodos.length !== todos.length) {
|
|
427
|
+
setTodos(validTodos)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
updateMetrics('optimizeTodoStorage')
|
|
431
|
+
}
|