@within-7/minto 0.2.0 → 0.3.3
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/commands/agents/AgentsCommand.js +22 -24
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/context.js +2 -1
- package/dist/commands/context.js.map +2 -2
- package/dist/commands/export.js +2 -1
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/mcp-interactive.js +7 -6
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +3 -2
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/permissions.js +4 -3
- package/dist/commands/permissions.js.map +2 -2
- package/dist/commands/plugin/AddMarketplaceForm.js +3 -2
- package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
- package/dist/commands/plugin/ConfirmDialog.js +2 -1
- package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
- package/dist/commands/plugin/ErrorView.js +2 -1
- package/dist/commands/plugin/ErrorView.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js +5 -4
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsManager.js +5 -4
- package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
- package/dist/commands/plugin/MainMenu.js +2 -1
- package/dist/commands/plugin/MainMenu.js.map +2 -2
- package/dist/commands/plugin/MarketplaceManager.js +5 -4
- package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
- package/dist/commands/plugin/MarketplaceSelector.js +4 -3
- package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
- package/dist/commands/plugin/PlaceholderScreen.js +3 -2
- package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
- package/dist/commands/plugin/PluginBrowser.js +6 -5
- package/dist/commands/plugin/PluginBrowser.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsInstall.js +5 -4
- package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsManage.js +4 -3
- package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
- package/dist/commands/plugin.js +16 -15
- package/dist/commands/plugin.js.map +2 -2
- package/dist/commands/sandbox.js +4 -3
- package/dist/commands/sandbox.js.map +2 -2
- package/dist/commands/setup.js +2 -1
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +2 -2
- package/dist/commands/undo.js +245 -0
- package/dist/commands/undo.js.map +7 -0
- package/dist/commands.js +2 -0
- package/dist/commands.js.map +2 -2
- package/dist/components/AgentThinkingBlock.js +1 -1
- package/dist/components/AgentThinkingBlock.js.map +2 -2
- package/dist/components/AsciiLogo.js +7 -8
- package/dist/components/AsciiLogo.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/QuestionView.js +2 -1
- package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
- package/dist/components/CollapsibleHint.js +2 -1
- package/dist/components/CollapsibleHint.js.map +2 -2
- package/dist/components/Config.js +3 -2
- package/dist/components/Config.js.map +2 -2
- package/dist/components/ConsoleOAuthFlow.js +2 -1
- package/dist/components/ConsoleOAuthFlow.js.map +2 -2
- package/dist/components/Cost.js +2 -1
- package/dist/components/Cost.js.map +2 -2
- package/dist/components/HeaderBar.js +13 -8
- package/dist/components/HeaderBar.js.map +2 -2
- package/dist/components/HistorySearchOverlay.js +4 -3
- package/dist/components/HistorySearchOverlay.js.map +2 -2
- package/dist/components/HotkeyHelpPanel.js +8 -11
- package/dist/components/HotkeyHelpPanel.js.map +2 -2
- package/dist/components/InvalidConfigDialog.js +2 -1
- package/dist/components/InvalidConfigDialog.js.map +2 -2
- package/dist/components/Logo.js +23 -67
- package/dist/components/Logo.js.map +2 -2
- package/dist/components/MCPServerApprovalDialog.js +2 -1
- package/dist/components/MCPServerApprovalDialog.js.map +2 -2
- package/dist/components/MCPServerDialogCopy.js +2 -1
- package/dist/components/MCPServerDialogCopy.js.map +2 -2
- package/dist/components/MCPServerMultiselectDialog.js +2 -1
- package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
- package/dist/components/MessageSelector.js +4 -3
- package/dist/components/MessageSelector.js.map +2 -2
- package/dist/components/ModeIndicator.js +2 -1
- package/dist/components/ModeIndicator.js.map +2 -2
- package/dist/components/ModelConfig.js +4 -3
- package/dist/components/ModelConfig.js.map +2 -2
- package/dist/components/ModelListManager.js +4 -3
- package/dist/components/ModelListManager.js.map +2 -2
- package/dist/components/ModelSelector/ModelSelector.js +26 -13
- package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
- package/dist/components/Onboarding.js +3 -2
- package/dist/components/Onboarding.js.map +2 -2
- package/dist/components/OperationSummary.js +130 -0
- package/dist/components/OperationSummary.js.map +7 -0
- package/dist/components/PromptInput.js +88 -75
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/SensitiveFileWarning.js +31 -0
- package/dist/components/SensitiveFileWarning.js.map +7 -0
- package/dist/components/Spinner.js +71 -22
- package/dist/components/Spinner.js.map +2 -2
- package/dist/components/StructuredDiff.js +6 -8
- package/dist/components/StructuredDiff.js.map +2 -2
- package/dist/components/SubagentBlock.js +4 -2
- package/dist/components/SubagentBlock.js.map +2 -2
- package/dist/components/SubagentProgress.js +17 -6
- package/dist/components/SubagentProgress.js.map +2 -2
- package/dist/components/TaskCard.js +14 -11
- package/dist/components/TaskCard.js.map +2 -2
- package/dist/components/TextInput.js +9 -1
- package/dist/components/TextInput.js.map +2 -2
- package/dist/components/TodoPanel.js +44 -26
- package/dist/components/TodoPanel.js.map +2 -2
- package/dist/components/ToolUseLoader.js +2 -2
- package/dist/components/ToolUseLoader.js.map +2 -2
- package/dist/components/TreeConnector.js +4 -3
- package/dist/components/TreeConnector.js.map +2 -2
- package/dist/components/TrustDialog.js +2 -1
- package/dist/components/TrustDialog.js.map +2 -2
- package/dist/components/binary-feedback/BinaryFeedbackView.js +2 -1
- package/dist/components/binary-feedback/BinaryFeedbackView.js.map +2 -2
- package/dist/components/messages/AssistantTextMessage.js +17 -9
- package/dist/components/messages/AssistantTextMessage.js.map +2 -2
- package/dist/components/messages/AssistantToolUseMessage.js +8 -4
- package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
- package/dist/components/messages/GroupRenderer.js +2 -1
- package/dist/components/messages/GroupRenderer.js.map +2 -2
- package/dist/components/messages/NestedTasksPreview.js +13 -1
- package/dist/components/messages/NestedTasksPreview.js.map +2 -2
- package/dist/components/messages/ParallelTasksGroupView.js +4 -3
- package/dist/components/messages/ParallelTasksGroupView.js.map +2 -2
- package/dist/components/messages/TaskInModuleView.js +35 -15
- package/dist/components/messages/TaskInModuleView.js.map +2 -2
- package/dist/components/messages/TaskOutputContent.js +9 -6
- package/dist/components/messages/TaskOutputContent.js.map +2 -2
- package/dist/components/messages/UserPromptMessage.js +2 -2
- package/dist/components/messages/UserPromptMessage.js.map +2 -2
- package/dist/constants/colors.js +90 -72
- package/dist/constants/colors.js.map +2 -2
- package/dist/constants/prompts.js +22 -1
- package/dist/constants/prompts.js.map +2 -2
- package/dist/constants/toolInputExamples.js +84 -0
- package/dist/constants/toolInputExamples.js.map +7 -0
- package/dist/core/backupManager.js +321 -0
- package/dist/core/backupManager.js.map +7 -0
- package/dist/core/costTracker.js +9 -18
- package/dist/core/costTracker.js.map +2 -2
- package/dist/core/gitAutoCommit.js +287 -0
- package/dist/core/gitAutoCommit.js.map +7 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +2 -2
- package/dist/core/operationTracker.js +212 -0
- package/dist/core/operationTracker.js.map +7 -0
- package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
- package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
- package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
- package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
- package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
- package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
- package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
- package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
- package/dist/core/tokenStats.js +9 -0
- package/dist/core/tokenStats.js.map +7 -0
- package/dist/core/tokenStatsManager.js +331 -0
- package/dist/core/tokenStatsManager.js.map +7 -0
- package/dist/entrypoints/cli.js +122 -88
- package/dist/entrypoints/cli.js.map +2 -2
- package/dist/hooks/useAgentTokenStats.js +72 -0
- package/dist/hooks/useAgentTokenStats.js.map +7 -0
- package/dist/hooks/useAgentTranscripts.js +30 -6
- package/dist/hooks/useAgentTranscripts.js.map +2 -2
- package/dist/hooks/useLogMessages.js +12 -1
- package/dist/hooks/useLogMessages.js.map +2 -2
- package/dist/i18n/locales/en.js +6 -5
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +6 -5
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/i18n/types.js.map +1 -1
- package/dist/permissions.js +147 -1
- package/dist/permissions.js.map +2 -2
- package/dist/query.js +78 -4
- package/dist/query.js.map +3 -3
- package/dist/screens/REPL.js +23 -3
- package/dist/screens/REPL.js.map +2 -2
- package/dist/screens/ResumeConversation.js +2 -0
- package/dist/screens/ResumeConversation.js.map +2 -2
- package/dist/services/claude.js +54 -3
- package/dist/services/claude.js.map +2 -2
- package/dist/services/intelligentCompactor.js +1 -1
- package/dist/services/intelligentCompactor.js.map +2 -2
- package/dist/services/mcpClient.js +81 -25
- package/dist/services/mcpClient.js.map +2 -2
- package/dist/services/sandbox/filesystemBoundary.js +58 -17
- package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
- package/dist/services/taskStore.js +205 -0
- package/dist/services/taskStore.js.map +7 -0
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +42 -4
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
- package/dist/tools/BashTool/BashTool.js +43 -7
- package/dist/tools/BashTool/BashTool.js.map +2 -2
- package/dist/tools/BashTool/prompt.js +184 -34
- package/dist/tools/BashTool/prompt.js.map +2 -2
- package/dist/tools/FileEditTool/FileEditTool.js +24 -9
- package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
- package/dist/tools/FileEditTool/prompt.js +10 -4
- package/dist/tools/FileEditTool/prompt.js.map +2 -2
- package/dist/tools/FileEditTool/utils.js +10 -4
- package/dist/tools/FileEditTool/utils.js.map +2 -2
- package/dist/tools/FileReadTool/FileReadTool.js +1 -1
- package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
- package/dist/tools/FileReadTool/prompt.js +16 -1
- package/dist/tools/FileReadTool/prompt.js.map +2 -2
- package/dist/tools/FileWriteTool/FileWriteTool.js +1 -1
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +1 -1
- package/dist/tools/FileWriteTool/prompt.js +12 -3
- package/dist/tools/FileWriteTool/prompt.js.map +2 -2
- package/dist/tools/GlobTool/prompt.js +12 -1
- package/dist/tools/GlobTool/prompt.js.map +2 -2
- package/dist/tools/GrepTool/GrepTool.js +333 -65
- package/dist/tools/GrepTool/GrepTool.js.map +2 -2
- package/dist/tools/GrepTool/prompt.js +15 -8
- package/dist/tools/GrepTool/prompt.js.map +2 -2
- package/dist/tools/MultiEditTool/prompt.js +5 -3
- package/dist/tools/MultiEditTool/prompt.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +59 -46
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/NotebookEditTool/prompt.js +1 -1
- package/dist/tools/NotebookEditTool/prompt.js.map +1 -1
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
- package/dist/tools/PlanModeTool/prompt.js +1 -1
- package/dist/tools/PlanModeTool/prompt.js.map +1 -1
- package/dist/tools/SkillTool/SkillTool.js +4 -3
- package/dist/tools/SkillTool/SkillTool.js.map +2 -2
- package/dist/tools/SkillTool/prompt.js +1 -1
- package/dist/tools/SkillTool/prompt.js.map +1 -1
- package/dist/tools/TaskCreateTool/TaskCreateTool.js +102 -0
- package/dist/tools/TaskCreateTool/TaskCreateTool.js.map +7 -0
- package/dist/tools/TaskCreateTool/prompt.js +47 -0
- package/dist/tools/TaskCreateTool/prompt.js.map +7 -0
- package/dist/tools/TaskGetTool/TaskGetTool.js +115 -0
- package/dist/tools/TaskGetTool/TaskGetTool.js.map +7 -0
- package/dist/tools/TaskGetTool/prompt.js +28 -0
- package/dist/tools/TaskGetTool/prompt.js.map +7 -0
- package/dist/tools/TaskListTool/TaskListTool.js +102 -0
- package/dist/tools/TaskListTool/TaskListTool.js.map +7 -0
- package/dist/tools/TaskListTool/prompt.js +27 -0
- package/dist/tools/TaskListTool/prompt.js.map +7 -0
- package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
- package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
- package/dist/tools/TaskStopTool/TaskStopTool.js +150 -0
- package/dist/tools/TaskStopTool/TaskStopTool.js.map +7 -0
- package/dist/tools/TaskStopTool/prompt.js +15 -0
- package/dist/tools/TaskStopTool/prompt.js.map +7 -0
- package/dist/tools/TaskTool/TaskTool.js +49 -1
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/tools/TaskUpdateTool/TaskUpdateTool.js +134 -0
- package/dist/tools/TaskUpdateTool/TaskUpdateTool.js.map +7 -0
- package/dist/tools/TaskUpdateTool/prompt.js +81 -0
- package/dist/tools/TaskUpdateTool/prompt.js.map +7 -0
- package/dist/tools/URLFetcherTool/prompt.js +1 -1
- package/dist/tools/URLFetcherTool/prompt.js.map +1 -1
- package/dist/tools.js +12 -0
- package/dist/tools.js.map +2 -2
- package/dist/utils/CircuitBreaker.js +242 -0
- package/dist/utils/CircuitBreaker.js.map +7 -0
- package/dist/utils/ask.js +2 -0
- package/dist/utils/ask.js.map +2 -2
- package/dist/utils/config.js +47 -5
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/credentials/CredentialStore.js +1 -0
- package/dist/utils/credentials/CredentialStore.js.map +7 -0
- package/dist/utils/credentials/EncryptedFileStore.js +157 -0
- package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
- package/dist/utils/credentials/index.js +37 -0
- package/dist/utils/credentials/index.js.map +7 -0
- package/dist/utils/credentials/migration.js +82 -0
- package/dist/utils/credentials/migration.js.map +7 -0
- package/dist/utils/markdown.js +13 -1
- package/dist/utils/markdown.js.map +2 -2
- package/dist/utils/model.js +15 -2
- package/dist/utils/model.js.map +2 -2
- package/dist/utils/permissions/filesystem.js +5 -1
- package/dist/utils/permissions/filesystem.js.map +2 -2
- package/dist/utils/ripgrep.js +53 -1
- package/dist/utils/ripgrep.js.map +2 -2
- package/dist/utils/safePath.js +132 -0
- package/dist/utils/safePath.js.map +7 -0
- package/dist/utils/sensitiveFiles.js +125 -0
- package/dist/utils/sensitiveFiles.js.map +7 -0
- package/dist/utils/taskDisplayUtils.js +9 -9
- package/dist/utils/taskDisplayUtils.js.map +2 -2
- package/dist/utils/terminal.js +12 -0
- package/dist/utils/terminal.js.map +2 -2
- package/dist/utils/theme.js +6 -6
- package/dist/utils/theme.js.map +1 -1
- package/dist/utils/toolRiskClassification.js +207 -0
- package/dist/utils/toolRiskClassification.js.map +7 -0
- package/dist/utils/tooling/safeRender.js +17 -17
- package/dist/utils/tooling/safeRender.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +22 -28
- package/dist/hooks/useCancelRequest.js +0 -31
- package/dist/hooks/useCancelRequest.js.map +0 -7
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
class CircuitOpenError extends Error {
|
|
2
|
+
constructor(remainingCooldown, serverName) {
|
|
3
|
+
const message = serverName ? `Circuit breaker for "${serverName}" is open. Retry in ${Math.ceil(remainingCooldown / 1e3)}s` : `Circuit breaker is open. Retry in ${Math.ceil(remainingCooldown / 1e3)}s`;
|
|
4
|
+
super(message);
|
|
5
|
+
this.remainingCooldown = remainingCooldown;
|
|
6
|
+
this.name = "CircuitOpenError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class CircuitBreaker {
|
|
10
|
+
state = "closed";
|
|
11
|
+
failureCount = 0;
|
|
12
|
+
successCount = 0;
|
|
13
|
+
lastFailureTime = null;
|
|
14
|
+
config;
|
|
15
|
+
listeners = /* @__PURE__ */ new Set();
|
|
16
|
+
name;
|
|
17
|
+
totalRequests = 0;
|
|
18
|
+
totalFailures = 0;
|
|
19
|
+
constructor(name = "default", config) {
|
|
20
|
+
this.name = name;
|
|
21
|
+
this.config = {
|
|
22
|
+
failureThreshold: config?.failureThreshold ?? 5,
|
|
23
|
+
successThreshold: config?.successThreshold ?? 2,
|
|
24
|
+
openTimeout: config?.openTimeout ?? 3e4,
|
|
25
|
+
halfOpenTimeout: config?.halfOpenTimeout ?? 3e4
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Execute an operation through the circuit breaker
|
|
30
|
+
* @throws {CircuitOpenError} If circuit is open
|
|
31
|
+
* @throws {Error} If operation fails
|
|
32
|
+
*/
|
|
33
|
+
async execute(operation) {
|
|
34
|
+
this.totalRequests++;
|
|
35
|
+
if (this.state === "open") {
|
|
36
|
+
const cooldownRemaining = this.getRemainingCooldown();
|
|
37
|
+
if (cooldownRemaining <= 0) {
|
|
38
|
+
this.transition("half-open");
|
|
39
|
+
} else {
|
|
40
|
+
const error = new CircuitOpenError(cooldownRemaining, this.name);
|
|
41
|
+
this.notifyListeners("failure", { error });
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const result = await operation();
|
|
47
|
+
this.onSuccess();
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.onFailure(error instanceof Error ? error : new Error(String(error)));
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Execute with timeout protection
|
|
56
|
+
* @throws {CircuitOpenError} If circuit is open
|
|
57
|
+
* @throws {Error} If operation times out or fails
|
|
58
|
+
*/
|
|
59
|
+
async executeWithTimeout(operation, timeoutMs) {
|
|
60
|
+
const timeout = timeoutMs ?? this.config.halfOpenTimeout;
|
|
61
|
+
if (!timeout) {
|
|
62
|
+
return this.execute(operation);
|
|
63
|
+
}
|
|
64
|
+
return Promise.race([
|
|
65
|
+
this.execute(operation),
|
|
66
|
+
new Promise(
|
|
67
|
+
(_, reject) => setTimeout(
|
|
68
|
+
() => reject(new Error(`Operation timeout after ${timeout}ms`)),
|
|
69
|
+
timeout
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get current circuit state
|
|
76
|
+
*/
|
|
77
|
+
getState() {
|
|
78
|
+
return this.state;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get detailed statistics
|
|
82
|
+
*/
|
|
83
|
+
getStats() {
|
|
84
|
+
return {
|
|
85
|
+
state: this.state,
|
|
86
|
+
failureCount: this.failureCount,
|
|
87
|
+
successCount: this.successCount,
|
|
88
|
+
lastFailureTime: this.lastFailureTime,
|
|
89
|
+
totalRequests: this.totalRequests,
|
|
90
|
+
totalFailures: this.totalFailures
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get remaining cooldown time in milliseconds
|
|
95
|
+
*/
|
|
96
|
+
getRemainingCooldown() {
|
|
97
|
+
if (!this.lastFailureTime) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
return Math.max(
|
|
101
|
+
0,
|
|
102
|
+
this.config.openTimeout - (Date.now() - this.lastFailureTime)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Register a listener for state changes and events
|
|
107
|
+
*/
|
|
108
|
+
on(listener) {
|
|
109
|
+
this.listeners.add(listener);
|
|
110
|
+
return () => {
|
|
111
|
+
this.listeners.delete(listener);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reset the circuit breaker to CLOSED state
|
|
116
|
+
*/
|
|
117
|
+
reset() {
|
|
118
|
+
const previousState = this.state;
|
|
119
|
+
this.state = "closed";
|
|
120
|
+
this.failureCount = 0;
|
|
121
|
+
this.successCount = 0;
|
|
122
|
+
this.lastFailureTime = null;
|
|
123
|
+
this.totalRequests = 0;
|
|
124
|
+
this.totalFailures = 0;
|
|
125
|
+
this.notifyListeners("state-change", {
|
|
126
|
+
state: this.state,
|
|
127
|
+
previousState
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Manually open the circuit (for testing or explicit fault injection)
|
|
132
|
+
*/
|
|
133
|
+
forceOpen() {
|
|
134
|
+
this.transition("open");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Manually close the circuit (for recovery testing)
|
|
138
|
+
*/
|
|
139
|
+
forceClosed() {
|
|
140
|
+
this.transition("closed");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Manually transition to half-open state (for testing)
|
|
144
|
+
*/
|
|
145
|
+
forceHalfOpen() {
|
|
146
|
+
this.transition("half-open");
|
|
147
|
+
}
|
|
148
|
+
// Private methods
|
|
149
|
+
transition(newState) {
|
|
150
|
+
if (newState === this.state) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const previousState = this.state;
|
|
154
|
+
this.state = newState;
|
|
155
|
+
if (newState === "closed") {
|
|
156
|
+
this.failureCount = 0;
|
|
157
|
+
this.successCount = 0;
|
|
158
|
+
} else if (newState === "half-open") {
|
|
159
|
+
this.successCount = 0;
|
|
160
|
+
this.failureCount = 0;
|
|
161
|
+
}
|
|
162
|
+
this.notifyListeners("state-change", {
|
|
163
|
+
state: newState,
|
|
164
|
+
previousState
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
onSuccess() {
|
|
168
|
+
if (this.state === "half-open") {
|
|
169
|
+
this.successCount++;
|
|
170
|
+
if (this.successCount >= this.config.successThreshold) {
|
|
171
|
+
this.transition("closed");
|
|
172
|
+
}
|
|
173
|
+
} else if (this.state === "closed") {
|
|
174
|
+
this.failureCount = 0;
|
|
175
|
+
}
|
|
176
|
+
this.notifyListeners("success", {});
|
|
177
|
+
}
|
|
178
|
+
onFailure(error) {
|
|
179
|
+
this.failureCount++;
|
|
180
|
+
this.totalFailures++;
|
|
181
|
+
this.lastFailureTime = Date.now();
|
|
182
|
+
if (this.state === "closed") {
|
|
183
|
+
if (this.failureCount >= this.config.failureThreshold) {
|
|
184
|
+
this.transition("open");
|
|
185
|
+
}
|
|
186
|
+
} else if (this.state === "half-open") {
|
|
187
|
+
this.transition("open");
|
|
188
|
+
}
|
|
189
|
+
this.notifyListeners("failure", { error });
|
|
190
|
+
}
|
|
191
|
+
notifyListeners(event, details) {
|
|
192
|
+
for (const listener of this.listeners) {
|
|
193
|
+
try {
|
|
194
|
+
listener(event, details);
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
class CircuitBreakerRegistry {
|
|
201
|
+
breakers = /* @__PURE__ */ new Map();
|
|
202
|
+
defaultConfig;
|
|
203
|
+
constructor(defaultConfig) {
|
|
204
|
+
this.defaultConfig = defaultConfig;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get or create a circuit breaker by name
|
|
208
|
+
*/
|
|
209
|
+
getOrCreate(name, config) {
|
|
210
|
+
if (!this.breakers.has(name)) {
|
|
211
|
+
const mergedConfig = { ...this.defaultConfig, ...config };
|
|
212
|
+
this.breakers.set(name, new CircuitBreaker(name, mergedConfig));
|
|
213
|
+
}
|
|
214
|
+
return this.breakers.get(name);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get all registered circuit breakers
|
|
218
|
+
*/
|
|
219
|
+
getAll() {
|
|
220
|
+
return new Map(this.breakers);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Reset all circuit breakers
|
|
224
|
+
*/
|
|
225
|
+
resetAll() {
|
|
226
|
+
for (const breaker of this.breakers.values()) {
|
|
227
|
+
breaker.reset();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Clear all circuit breakers
|
|
232
|
+
*/
|
|
233
|
+
clear() {
|
|
234
|
+
this.breakers.clear();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
CircuitBreaker,
|
|
239
|
+
CircuitBreakerRegistry,
|
|
240
|
+
CircuitOpenError
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=CircuitBreaker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/CircuitBreaker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Circuit Breaker pattern implementation for handling failing services gracefully\n *\n * States:\n * - CLOSED: Service is healthy, requests pass through normally\n * - OPEN: Service is unhealthy, requests fail immediately\n * - HALF_OPEN: Testing if service has recovered, limited requests allowed\n *\n * Transitions:\n * - CLOSED \u2192 OPEN: When failure count exceeds threshold\n * - OPEN \u2192 HALF_OPEN: After cooldown timeout\n * - HALF_OPEN \u2192 CLOSED: When success count exceeds threshold\n * - HALF_OPEN \u2192 OPEN: When a request fails\n */\n\nexport interface CircuitBreakerConfig {\n /** Number of failures that trigger opening the circuit (default: 5) */\n failureThreshold: number\n /** Number of successful requests needed to close circuit from HALF_OPEN (default: 2) */\n successThreshold: number\n /** Time in ms before transitioning from OPEN to HALF_OPEN (default: 30000) */\n openTimeout: number\n /** Optional timeout in ms for operations (default: none) */\n halfOpenTimeout?: number\n}\n\nexport class CircuitOpenError extends Error {\n constructor(\n public readonly remainingCooldown: number,\n serverName?: string,\n ) {\n const message = serverName\n ? `Circuit breaker for \"${serverName}\" is open. Retry in ${Math.ceil(remainingCooldown / 1000)}s`\n : `Circuit breaker is open. Retry in ${Math.ceil(remainingCooldown / 1000)}s`\n super(message)\n this.name = 'CircuitOpenError'\n }\n}\n\nexport type CircuitBreakerState = 'closed' | 'open' | 'half-open'\n\nexport interface CircuitBreakerStats {\n state: CircuitBreakerState\n failureCount: number\n successCount: number\n lastFailureTime: number | null\n totalRequests: number\n totalFailures: number\n}\n\nexport type CircuitBreakerListener = (\n event: 'state-change' | 'failure' | 'success',\n details: {\n state?: CircuitBreakerState\n previousState?: CircuitBreakerState\n error?: Error\n },\n) => void\n\n/**\n * Circuit Breaker implementation for fault tolerance\n *\n * Prevents cascading failures by failing fast when a service is unhealthy,\n * while automatically attempting recovery when the service potentially recovers.\n */\nexport class CircuitBreaker<T = unknown> {\n private state: CircuitBreakerState = 'closed'\n private failureCount: number = 0\n private successCount: number = 0\n private lastFailureTime: number | null = null\n private readonly config: Required<CircuitBreakerConfig>\n private readonly listeners: Set<CircuitBreakerListener> = new Set()\n private readonly name: string\n private totalRequests: number = 0\n private totalFailures: number = 0\n\n constructor(\n name: string = 'default',\n config?: Partial<CircuitBreakerConfig>,\n ) {\n this.name = name\n this.config = {\n failureThreshold: config?.failureThreshold ?? 5,\n successThreshold: config?.successThreshold ?? 2,\n openTimeout: config?.openTimeout ?? 30000,\n halfOpenTimeout: config?.halfOpenTimeout ?? 30000,\n }\n }\n\n /**\n * Execute an operation through the circuit breaker\n * @throws {CircuitOpenError} If circuit is open\n * @throws {Error} If operation fails\n */\n async execute<R>(operation: () => Promise<R>): Promise<R> {\n this.totalRequests++\n\n // Check if we need to transition from OPEN to HALF_OPEN\n if (this.state === 'open') {\n const cooldownRemaining = this.getRemainingCooldown()\n if (cooldownRemaining <= 0) {\n this.transition('half-open')\n } else {\n const error = new CircuitOpenError(cooldownRemaining, this.name)\n this.notifyListeners('failure', { error })\n throw error\n }\n }\n\n // Execute the operation\n try {\n const result = await operation()\n this.onSuccess()\n return result\n } catch (error) {\n this.onFailure(error instanceof Error ? error : new Error(String(error)))\n throw error\n }\n }\n\n /**\n * Execute with timeout protection\n * @throws {CircuitOpenError} If circuit is open\n * @throws {Error} If operation times out or fails\n */\n async executeWithTimeout<R>(\n operation: () => Promise<R>,\n timeoutMs?: number,\n ): Promise<R> {\n const timeout = timeoutMs ?? this.config.halfOpenTimeout\n if (!timeout) {\n return this.execute(operation)\n }\n\n return Promise.race([\n this.execute(operation),\n new Promise<R>((_, reject) =>\n setTimeout(\n () => reject(new Error(`Operation timeout after ${timeout}ms`)),\n timeout,\n ),\n ),\n ])\n }\n\n /**\n * Get current circuit state\n */\n getState(): CircuitBreakerState {\n return this.state\n }\n\n /**\n * Get detailed statistics\n */\n getStats(): CircuitBreakerStats {\n return {\n state: this.state,\n failureCount: this.failureCount,\n successCount: this.successCount,\n lastFailureTime: this.lastFailureTime,\n totalRequests: this.totalRequests,\n totalFailures: this.totalFailures,\n }\n }\n\n /**\n * Get remaining cooldown time in milliseconds\n */\n getRemainingCooldown(): number {\n if (!this.lastFailureTime) {\n return 0\n }\n return Math.max(\n 0,\n this.config.openTimeout - (Date.now() - this.lastFailureTime),\n )\n }\n\n /**\n * Register a listener for state changes and events\n */\n on(listener: CircuitBreakerListener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Reset the circuit breaker to CLOSED state\n */\n reset(): void {\n const previousState = this.state\n this.state = 'closed'\n this.failureCount = 0\n this.successCount = 0\n this.lastFailureTime = null\n this.totalRequests = 0\n this.totalFailures = 0\n\n this.notifyListeners('state-change', {\n state: this.state,\n previousState,\n })\n }\n\n /**\n * Manually open the circuit (for testing or explicit fault injection)\n */\n forceOpen(): void {\n this.transition('open')\n }\n\n /**\n * Manually close the circuit (for recovery testing)\n */\n forceClosed(): void {\n this.transition('closed')\n }\n\n /**\n * Manually transition to half-open state (for testing)\n */\n forceHalfOpen(): void {\n this.transition('half-open')\n }\n\n // Private methods\n\n private transition(newState: CircuitBreakerState): void {\n if (newState === this.state) {\n return\n }\n\n const previousState = this.state\n this.state = newState\n\n // Reset counters on state transition\n if (newState === 'closed') {\n this.failureCount = 0\n this.successCount = 0\n } else if (newState === 'half-open') {\n this.successCount = 0\n this.failureCount = 0\n }\n\n this.notifyListeners('state-change', {\n state: newState,\n previousState,\n })\n }\n\n private onSuccess(): void {\n if (this.state === 'half-open') {\n this.successCount++\n if (this.successCount >= this.config.successThreshold) {\n this.transition('closed')\n }\n } else if (this.state === 'closed') {\n this.failureCount = 0\n }\n\n this.notifyListeners('success', {})\n }\n\n private onFailure(error: Error): void {\n this.failureCount++\n this.totalFailures++\n this.lastFailureTime = Date.now()\n\n if (this.state === 'closed') {\n if (this.failureCount >= this.config.failureThreshold) {\n this.transition('open')\n }\n } else if (this.state === 'half-open') {\n // Any failure in half-open state goes back to open\n this.transition('open')\n }\n\n this.notifyListeners('failure', { error })\n }\n\n private notifyListeners(\n event: 'state-change' | 'failure' | 'success',\n details: {\n state?: CircuitBreakerState\n previousState?: CircuitBreakerState\n error?: Error\n },\n ): void {\n for (const listener of this.listeners) {\n try {\n listener(event, details)\n } catch {\n // Ignore listener errors to prevent them from affecting the circuit\n }\n }\n }\n}\n\n/**\n * Create a simple factory for managing multiple circuit breakers\n */\nexport class CircuitBreakerRegistry {\n private readonly breakers = new Map<string, CircuitBreaker>()\n private readonly defaultConfig?: Partial<CircuitBreakerConfig>\n\n constructor(defaultConfig?: Partial<CircuitBreakerConfig>) {\n this.defaultConfig = defaultConfig\n }\n\n /**\n * Get or create a circuit breaker by name\n */\n getOrCreate(\n name: string,\n config?: Partial<CircuitBreakerConfig>,\n ): CircuitBreaker {\n if (!this.breakers.has(name)) {\n const mergedConfig = { ...this.defaultConfig, ...config }\n this.breakers.set(name, new CircuitBreaker(name, mergedConfig))\n }\n return this.breakers.get(name)!\n }\n\n /**\n * Get all registered circuit breakers\n */\n getAll(): Map<string, CircuitBreaker> {\n return new Map(this.breakers)\n }\n\n /**\n * Reset all circuit breakers\n */\n resetAll(): void {\n for (const breaker of this.breakers.values()) {\n breaker.reset()\n }\n }\n\n /**\n * Clear all circuit breakers\n */\n clear(): void {\n this.breakers.clear()\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/utils/ask.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createUserMessage } from "./messages.js";
|
|
|
10
10
|
async function ask({
|
|
11
11
|
commands,
|
|
12
12
|
safeMode,
|
|
13
|
+
safetyMode = "yolo",
|
|
13
14
|
hasPermissionsToUseTool,
|
|
14
15
|
messageLogName,
|
|
15
16
|
prompt,
|
|
@@ -36,6 +37,7 @@ async function ask({
|
|
|
36
37
|
tools,
|
|
37
38
|
verbose,
|
|
38
39
|
safeMode,
|
|
40
|
+
safetyMode,
|
|
39
41
|
forkNumber: 0,
|
|
40
42
|
messageLogName: "unused",
|
|
41
43
|
maxThinkingTokens: 0
|
package/dist/utils/ask.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/ask.tsx"],
|
|
4
|
-
"sourcesContent": ["import { last } from 'lodash-es'\nimport { Command } from '@commands'\nimport { getSystemPrompt } from '@constants/prompts'\nimport { getContext } from '@context'\nimport { getTotalCost } from '@costTracker'\nimport { Message, query } from '@query'\nimport { CanUseToolFn } from '@hooks/useCanUseTool'\nimport { Tool } from '@tool'\nimport { getModelManager } from '@utils/model'\nimport { setCwd } from './state'\nimport { getMessagesPath, overwriteLog } from './log'\nimport { createUserMessage } from './messages'\n\ntype Props = {\n commands: Command[]\n safeMode?: boolean\n hasPermissionsToUseTool: CanUseToolFn\n messageLogName: string\n prompt: string\n cwd: string\n tools: Tool[]\n verbose?: boolean\n}\n\n// Sends a single prompt to the Anthropic Messages API and returns the response.\n// Assumes that claude is being used non-interactively -- will not\n// ask the user for permissions or further input.\nexport async function ask({\n commands,\n safeMode,\n hasPermissionsToUseTool,\n messageLogName,\n prompt,\n cwd,\n tools,\n verbose = false,\n}: Props): Promise<{\n resultText: string\n totalCost: number\n messageHistoryFile: string\n}> {\n await setCwd(cwd)\n const message = createUserMessage(prompt)\n const messages: Message[] = [message]\n\n const [systemPrompt, context, model] = await Promise.all([\n getSystemPrompt(),\n getContext(),\n getModelManager().getModelName('main'),\n ])\n\n for await (const m of query(\n messages,\n systemPrompt,\n context,\n hasPermissionsToUseTool,\n {\n options: {\n commands,\n tools,\n verbose,\n safeMode,\n forkNumber: 0,\n messageLogName: 'unused',\n maxThinkingTokens: 0,\n },\n abortController: new AbortController(),\n messageId: undefined,\n readFileTimestamps: {},\n setToolJSX: () => {}, // No-op function for non-interactive use\n },\n )) {\n messages.push(m)\n }\n\n const result = last(messages)\n if (!result || result.type !== 'assistant') {\n throw new Error('Expected content to be an assistant message')\n }\n if (result.message.content[0]?.type !== 'text') {\n throw new Error(\n `Expected first content item to be text, but got ${JSON.stringify(\n result.message.content[0],\n null,\n 2,\n )}`,\n )\n }\n\n // Write log that can be retrieved with `claude log`\n const messageHistoryFile = getMessagesPath(messageLogName, 0, 0)\n overwriteLog(messageHistoryFile, messages)\n\n return {\n resultText: result.message.content[0].text,\n totalCost: getTotalCost(),\n messageHistoryFile,\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,YAAY;AAErB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAkB,aAAa;AAG/B,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,iBAAiB,oBAAoB;AAC9C,SAAS,yBAAyB;
|
|
4
|
+
"sourcesContent": ["import { last } from 'lodash-es'\nimport { Command } from '@commands'\nimport { getSystemPrompt } from '@constants/prompts'\nimport { getContext } from '@context'\nimport { getTotalCost } from '@costTracker'\nimport { Message, query } from '@query'\nimport { CanUseToolFn } from '@hooks/useCanUseTool'\nimport { Tool } from '@tool'\nimport { getModelManager } from '@utils/model'\nimport { setCwd } from './state'\nimport { getMessagesPath, overwriteLog } from './log'\nimport { createUserMessage } from './messages'\nimport type { SafetyMode } from './config'\n\ntype Props = {\n commands: Command[]\n /** @deprecated Use safetyMode instead */\n safeMode?: boolean\n /** Safety mode: 'yolo' | 'smart' | 'strict' */\n safetyMode?: SafetyMode\n hasPermissionsToUseTool: CanUseToolFn\n messageLogName: string\n prompt: string\n cwd: string\n tools: Tool[]\n verbose?: boolean\n}\n\n// Sends a single prompt to the Anthropic Messages API and returns the response.\n// Assumes that claude is being used non-interactively -- will not\n// ask the user for permissions or further input.\nexport async function ask({\n commands,\n safeMode,\n safetyMode = 'yolo',\n hasPermissionsToUseTool,\n messageLogName,\n prompt,\n cwd,\n tools,\n verbose = false,\n}: Props): Promise<{\n resultText: string\n totalCost: number\n messageHistoryFile: string\n}> {\n await setCwd(cwd)\n const message = createUserMessage(prompt)\n const messages: Message[] = [message]\n\n const [systemPrompt, context, model] = await Promise.all([\n getSystemPrompt(),\n getContext(),\n getModelManager().getModelName('main'),\n ])\n\n for await (const m of query(\n messages,\n systemPrompt,\n context,\n hasPermissionsToUseTool,\n {\n options: {\n commands,\n tools,\n verbose,\n safeMode,\n safetyMode,\n forkNumber: 0,\n messageLogName: 'unused',\n maxThinkingTokens: 0,\n },\n abortController: new AbortController(),\n messageId: undefined,\n readFileTimestamps: {},\n setToolJSX: () => {}, // No-op function for non-interactive use\n },\n )) {\n messages.push(m)\n }\n\n const result = last(messages)\n if (!result || result.type !== 'assistant') {\n throw new Error('Expected content to be an assistant message')\n }\n if (result.message.content[0]?.type !== 'text') {\n throw new Error(\n `Expected first content item to be text, but got ${JSON.stringify(\n result.message.content[0],\n null,\n 2,\n )}`,\n )\n }\n\n // Write log that can be retrieved with `claude log`\n const messageHistoryFile = getMessagesPath(messageLogName, 0, 0)\n overwriteLog(messageHistoryFile, messages)\n\n return {\n resultText: result.message.content[0].text,\n totalCost: getTotalCost(),\n messageHistoryFile,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,YAAY;AAErB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAkB,aAAa;AAG/B,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,iBAAiB,oBAAoB;AAC9C,SAAS,yBAAyB;AAoBlC,eAAsB,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,GAIG;AACD,QAAM,OAAO,GAAG;AAChB,QAAM,UAAU,kBAAkB,MAAM;AACxC,QAAM,WAAsB,CAAC,OAAO;AAEpC,QAAM,CAAC,cAAc,SAAS,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IACvD,gBAAgB;AAAA,IAChB,WAAW;AAAA,IACX,gBAAgB,EAAE,aAAa,MAAM;AAAA,EACvC,CAAC;AAED,mBAAiB,KAAK;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,mBAAmB;AAAA,MACrB;AAAA,MACA,iBAAiB,IAAI,gBAAgB;AAAA,MACrC,WAAW;AAAA,MACX,oBAAoB,CAAC;AAAA,MACrB,YAAY,MAAM;AAAA,MAAC;AAAA;AAAA,IACrB;AAAA,EACF,GAAG;AACD,aAAS,KAAK,CAAC;AAAA,EACjB;AAEA,QAAM,SAAS,KAAK,QAAQ;AAC5B,MAAI,CAAC,UAAU,OAAO,SAAS,aAAa;AAC1C,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,MAAI,OAAO,QAAQ,QAAQ,CAAC,GAAG,SAAS,QAAQ;AAC9C,UAAM,IAAI;AAAA,MACR,mDAAmD,KAAK;AAAA,QACtD,OAAO,QAAQ,QAAQ,CAAC;AAAA,QACxB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,qBAAqB,gBAAgB,gBAAgB,GAAG,CAAC;AAC/D,eAAa,oBAAoB,QAAQ;AAEzC,SAAO;AAAA,IACL,YAAY,OAAO,QAAQ,QAAQ,CAAC,EAAE;AAAA,IACtC,WAAW,aAAa;AAAA,IACxB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { resolve, join } from "path";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from "fs";
|
|
2
|
+
import { resolve, join, dirname } from "path";
|
|
3
3
|
import { cloneDeep, memoize, pick } from "lodash-es";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { GLOBAL_CONFIG_FILE } from "./env.js";
|
|
@@ -83,8 +83,12 @@ const DEFAULT_GLOBAL_CONFIG = {
|
|
|
83
83
|
// Default to business consulting compression
|
|
84
84
|
thinking: false,
|
|
85
85
|
// Default to thinking mode off (Phase 4.2)
|
|
86
|
-
language: "en"
|
|
86
|
+
language: "en",
|
|
87
87
|
// Default to English
|
|
88
|
+
safeMode: false,
|
|
89
|
+
// Deprecated: use safetyMode instead
|
|
90
|
+
safetyMode: "yolo"
|
|
91
|
+
// Default to YOLO mode for non-technical users (zero interruption)
|
|
88
92
|
};
|
|
89
93
|
const GLOBAL_CONFIG_KEYS = [
|
|
90
94
|
"autoUpdaterStatus",
|
|
@@ -99,7 +103,8 @@ const GLOBAL_CONFIG_KEYS = [
|
|
|
99
103
|
"shiftEnterKeyBindingInstalled",
|
|
100
104
|
"maxTokens",
|
|
101
105
|
"compressionMode",
|
|
102
|
-
"language"
|
|
106
|
+
"language",
|
|
107
|
+
"safetyMode"
|
|
103
108
|
];
|
|
104
109
|
function isGlobalConfigKey(key) {
|
|
105
110
|
return GLOBAL_CONFIG_KEYS.includes(key);
|
|
@@ -194,6 +199,23 @@ function getCustomApiKeyStatus(truncatedApiKey) {
|
|
|
194
199
|
}
|
|
195
200
|
return "new";
|
|
196
201
|
}
|
|
202
|
+
function atomicWriteFileSync(filePath, content) {
|
|
203
|
+
const dir = dirname(filePath);
|
|
204
|
+
const tempPath = join(
|
|
205
|
+
dir,
|
|
206
|
+
`.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`
|
|
207
|
+
);
|
|
208
|
+
try {
|
|
209
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
210
|
+
renameSync(tempPath, filePath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
try {
|
|
213
|
+
rmSync(tempPath, { force: true });
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
197
219
|
function saveConfig(file, config, defaultConfig) {
|
|
198
220
|
const filteredConfig = Object.fromEntries(
|
|
199
221
|
Object.entries(config).filter(
|
|
@@ -201,7 +223,7 @@ function saveConfig(file, config, defaultConfig) {
|
|
|
201
223
|
)
|
|
202
224
|
);
|
|
203
225
|
try {
|
|
204
|
-
|
|
226
|
+
atomicWriteFileSync(file, JSON.stringify(filteredConfig, null, 2));
|
|
205
227
|
} catch (error) {
|
|
206
228
|
const err = error;
|
|
207
229
|
if (err?.code === "EACCES" || err?.code === "EPERM" || err?.code === "EROFS") {
|
|
@@ -554,6 +576,25 @@ function setModelPointer(pointer, modelName) {
|
|
|
554
576
|
reloadModelManager();
|
|
555
577
|
});
|
|
556
578
|
}
|
|
579
|
+
async function resolveApiKey(profile) {
|
|
580
|
+
if (!profile.apiKey) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
if (profile.apiKey.startsWith("encrypted:")) {
|
|
584
|
+
const modelName = profile.apiKey.slice("encrypted:".length);
|
|
585
|
+
try {
|
|
586
|
+
const { getApiKey } = await import("./credentials/index.js");
|
|
587
|
+
return getApiKey(modelName);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
590
|
+
console.warn(
|
|
591
|
+
`Failed to resolve encrypted API key for ${modelName}: ${errorMsg}`
|
|
592
|
+
);
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return profile.apiKey;
|
|
597
|
+
}
|
|
557
598
|
function isGPT5ModelName(modelName) {
|
|
558
599
|
if (!modelName || typeof modelName !== "string") return false;
|
|
559
600
|
const lowerName = modelName.toLowerCase();
|
|
@@ -711,6 +752,7 @@ export {
|
|
|
711
752
|
listConfigForCLI,
|
|
712
753
|
normalizeApiKeyForConfig,
|
|
713
754
|
removeMcprcServerForTesting,
|
|
755
|
+
resolveApiKey,
|
|
714
756
|
saveCurrentProjectConfig,
|
|
715
757
|
saveGlobalConfig,
|
|
716
758
|
setAllPointersToModel,
|