@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.
Files changed (308) hide show
  1. package/dist/commands/agents/AgentsCommand.js +22 -24
  2. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  3. package/dist/commands/context.js +2 -1
  4. package/dist/commands/context.js.map +2 -2
  5. package/dist/commands/export.js +2 -1
  6. package/dist/commands/export.js.map +2 -2
  7. package/dist/commands/mcp-interactive.js +7 -6
  8. package/dist/commands/mcp-interactive.js.map +2 -2
  9. package/dist/commands/model.js +3 -2
  10. package/dist/commands/model.js.map +2 -2
  11. package/dist/commands/permissions.js +4 -3
  12. package/dist/commands/permissions.js.map +2 -2
  13. package/dist/commands/plugin/AddMarketplaceForm.js +3 -2
  14. package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
  15. package/dist/commands/plugin/ConfirmDialog.js +2 -1
  16. package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
  17. package/dist/commands/plugin/ErrorView.js +2 -1
  18. package/dist/commands/plugin/ErrorView.js.map +2 -2
  19. package/dist/commands/plugin/InstalledPluginsByMarketplace.js +5 -4
  20. package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
  21. package/dist/commands/plugin/InstalledPluginsManager.js +5 -4
  22. package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
  23. package/dist/commands/plugin/MainMenu.js +2 -1
  24. package/dist/commands/plugin/MainMenu.js.map +2 -2
  25. package/dist/commands/plugin/MarketplaceManager.js +5 -4
  26. package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
  27. package/dist/commands/plugin/MarketplaceSelector.js +4 -3
  28. package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
  29. package/dist/commands/plugin/PlaceholderScreen.js +3 -2
  30. package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
  31. package/dist/commands/plugin/PluginBrowser.js +6 -5
  32. package/dist/commands/plugin/PluginBrowser.js.map +2 -2
  33. package/dist/commands/plugin/PluginDetailsInstall.js +5 -4
  34. package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
  35. package/dist/commands/plugin/PluginDetailsManage.js +4 -3
  36. package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
  37. package/dist/commands/plugin.js +16 -15
  38. package/dist/commands/plugin.js.map +2 -2
  39. package/dist/commands/sandbox.js +4 -3
  40. package/dist/commands/sandbox.js.map +2 -2
  41. package/dist/commands/setup.js +2 -1
  42. package/dist/commands/setup.js.map +2 -2
  43. package/dist/commands/status.js +2 -1
  44. package/dist/commands/status.js.map +2 -2
  45. package/dist/commands/undo.js +245 -0
  46. package/dist/commands/undo.js.map +7 -0
  47. package/dist/commands.js +2 -0
  48. package/dist/commands.js.map +2 -2
  49. package/dist/components/AgentThinkingBlock.js +1 -1
  50. package/dist/components/AgentThinkingBlock.js.map +2 -2
  51. package/dist/components/AsciiLogo.js +7 -8
  52. package/dist/components/AsciiLogo.js.map +2 -2
  53. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  54. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  55. package/dist/components/AskUserQuestionDialog/QuestionView.js +2 -1
  56. package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
  57. package/dist/components/CollapsibleHint.js +2 -1
  58. package/dist/components/CollapsibleHint.js.map +2 -2
  59. package/dist/components/Config.js +3 -2
  60. package/dist/components/Config.js.map +2 -2
  61. package/dist/components/ConsoleOAuthFlow.js +2 -1
  62. package/dist/components/ConsoleOAuthFlow.js.map +2 -2
  63. package/dist/components/Cost.js +2 -1
  64. package/dist/components/Cost.js.map +2 -2
  65. package/dist/components/HeaderBar.js +13 -8
  66. package/dist/components/HeaderBar.js.map +2 -2
  67. package/dist/components/HistorySearchOverlay.js +4 -3
  68. package/dist/components/HistorySearchOverlay.js.map +2 -2
  69. package/dist/components/HotkeyHelpPanel.js +8 -11
  70. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  71. package/dist/components/InvalidConfigDialog.js +2 -1
  72. package/dist/components/InvalidConfigDialog.js.map +2 -2
  73. package/dist/components/Logo.js +23 -67
  74. package/dist/components/Logo.js.map +2 -2
  75. package/dist/components/MCPServerApprovalDialog.js +2 -1
  76. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  77. package/dist/components/MCPServerDialogCopy.js +2 -1
  78. package/dist/components/MCPServerDialogCopy.js.map +2 -2
  79. package/dist/components/MCPServerMultiselectDialog.js +2 -1
  80. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  81. package/dist/components/MessageSelector.js +4 -3
  82. package/dist/components/MessageSelector.js.map +2 -2
  83. package/dist/components/ModeIndicator.js +2 -1
  84. package/dist/components/ModeIndicator.js.map +2 -2
  85. package/dist/components/ModelConfig.js +4 -3
  86. package/dist/components/ModelConfig.js.map +2 -2
  87. package/dist/components/ModelListManager.js +4 -3
  88. package/dist/components/ModelListManager.js.map +2 -2
  89. package/dist/components/ModelSelector/ModelSelector.js +26 -13
  90. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  91. package/dist/components/Onboarding.js +3 -2
  92. package/dist/components/Onboarding.js.map +2 -2
  93. package/dist/components/OperationSummary.js +130 -0
  94. package/dist/components/OperationSummary.js.map +7 -0
  95. package/dist/components/PromptInput.js +88 -75
  96. package/dist/components/PromptInput.js.map +2 -2
  97. package/dist/components/SensitiveFileWarning.js +31 -0
  98. package/dist/components/SensitiveFileWarning.js.map +7 -0
  99. package/dist/components/Spinner.js +71 -22
  100. package/dist/components/Spinner.js.map +2 -2
  101. package/dist/components/StructuredDiff.js +6 -8
  102. package/dist/components/StructuredDiff.js.map +2 -2
  103. package/dist/components/SubagentBlock.js +4 -2
  104. package/dist/components/SubagentBlock.js.map +2 -2
  105. package/dist/components/SubagentProgress.js +17 -6
  106. package/dist/components/SubagentProgress.js.map +2 -2
  107. package/dist/components/TaskCard.js +14 -11
  108. package/dist/components/TaskCard.js.map +2 -2
  109. package/dist/components/TextInput.js +9 -1
  110. package/dist/components/TextInput.js.map +2 -2
  111. package/dist/components/TodoPanel.js +44 -26
  112. package/dist/components/TodoPanel.js.map +2 -2
  113. package/dist/components/ToolUseLoader.js +2 -2
  114. package/dist/components/ToolUseLoader.js.map +2 -2
  115. package/dist/components/TreeConnector.js +4 -3
  116. package/dist/components/TreeConnector.js.map +2 -2
  117. package/dist/components/TrustDialog.js +2 -1
  118. package/dist/components/TrustDialog.js.map +2 -2
  119. package/dist/components/binary-feedback/BinaryFeedbackView.js +2 -1
  120. package/dist/components/binary-feedback/BinaryFeedbackView.js.map +2 -2
  121. package/dist/components/messages/AssistantTextMessage.js +17 -9
  122. package/dist/components/messages/AssistantTextMessage.js.map +2 -2
  123. package/dist/components/messages/AssistantToolUseMessage.js +8 -4
  124. package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
  125. package/dist/components/messages/GroupRenderer.js +2 -1
  126. package/dist/components/messages/GroupRenderer.js.map +2 -2
  127. package/dist/components/messages/NestedTasksPreview.js +13 -1
  128. package/dist/components/messages/NestedTasksPreview.js.map +2 -2
  129. package/dist/components/messages/ParallelTasksGroupView.js +4 -3
  130. package/dist/components/messages/ParallelTasksGroupView.js.map +2 -2
  131. package/dist/components/messages/TaskInModuleView.js +35 -15
  132. package/dist/components/messages/TaskInModuleView.js.map +2 -2
  133. package/dist/components/messages/TaskOutputContent.js +9 -6
  134. package/dist/components/messages/TaskOutputContent.js.map +2 -2
  135. package/dist/components/messages/UserPromptMessage.js +2 -2
  136. package/dist/components/messages/UserPromptMessage.js.map +2 -2
  137. package/dist/constants/colors.js +90 -72
  138. package/dist/constants/colors.js.map +2 -2
  139. package/dist/constants/prompts.js +22 -1
  140. package/dist/constants/prompts.js.map +2 -2
  141. package/dist/constants/toolInputExamples.js +84 -0
  142. package/dist/constants/toolInputExamples.js.map +7 -0
  143. package/dist/core/backupManager.js +321 -0
  144. package/dist/core/backupManager.js.map +7 -0
  145. package/dist/core/costTracker.js +9 -18
  146. package/dist/core/costTracker.js.map +2 -2
  147. package/dist/core/gitAutoCommit.js +287 -0
  148. package/dist/core/gitAutoCommit.js.map +7 -0
  149. package/dist/core/index.js +3 -0
  150. package/dist/core/index.js.map +2 -2
  151. package/dist/core/operationTracker.js +212 -0
  152. package/dist/core/operationTracker.js.map +7 -0
  153. package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
  154. package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
  155. package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
  156. package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
  157. package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
  158. package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
  159. package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
  160. package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
  161. package/dist/core/tokenStats.js +9 -0
  162. package/dist/core/tokenStats.js.map +7 -0
  163. package/dist/core/tokenStatsManager.js +331 -0
  164. package/dist/core/tokenStatsManager.js.map +7 -0
  165. package/dist/entrypoints/cli.js +122 -88
  166. package/dist/entrypoints/cli.js.map +2 -2
  167. package/dist/hooks/useAgentTokenStats.js +72 -0
  168. package/dist/hooks/useAgentTokenStats.js.map +7 -0
  169. package/dist/hooks/useAgentTranscripts.js +30 -6
  170. package/dist/hooks/useAgentTranscripts.js.map +2 -2
  171. package/dist/hooks/useLogMessages.js +12 -1
  172. package/dist/hooks/useLogMessages.js.map +2 -2
  173. package/dist/i18n/locales/en.js +6 -5
  174. package/dist/i18n/locales/en.js.map +2 -2
  175. package/dist/i18n/locales/zh-CN.js +6 -5
  176. package/dist/i18n/locales/zh-CN.js.map +2 -2
  177. package/dist/i18n/types.js.map +1 -1
  178. package/dist/permissions.js +147 -1
  179. package/dist/permissions.js.map +2 -2
  180. package/dist/query.js +78 -4
  181. package/dist/query.js.map +3 -3
  182. package/dist/screens/REPL.js +23 -3
  183. package/dist/screens/REPL.js.map +2 -2
  184. package/dist/screens/ResumeConversation.js +2 -0
  185. package/dist/screens/ResumeConversation.js.map +2 -2
  186. package/dist/services/claude.js +54 -3
  187. package/dist/services/claude.js.map +2 -2
  188. package/dist/services/intelligentCompactor.js +1 -1
  189. package/dist/services/intelligentCompactor.js.map +2 -2
  190. package/dist/services/mcpClient.js +81 -25
  191. package/dist/services/mcpClient.js.map +2 -2
  192. package/dist/services/sandbox/filesystemBoundary.js +58 -17
  193. package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
  194. package/dist/services/taskStore.js +205 -0
  195. package/dist/services/taskStore.js.map +7 -0
  196. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
  197. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
  198. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +42 -4
  199. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  200. package/dist/tools/BashTool/BashTool.js +43 -7
  201. package/dist/tools/BashTool/BashTool.js.map +2 -2
  202. package/dist/tools/BashTool/prompt.js +184 -34
  203. package/dist/tools/BashTool/prompt.js.map +2 -2
  204. package/dist/tools/FileEditTool/FileEditTool.js +24 -9
  205. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  206. package/dist/tools/FileEditTool/prompt.js +10 -4
  207. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  208. package/dist/tools/FileEditTool/utils.js +10 -4
  209. package/dist/tools/FileEditTool/utils.js.map +2 -2
  210. package/dist/tools/FileReadTool/FileReadTool.js +1 -1
  211. package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
  212. package/dist/tools/FileReadTool/prompt.js +16 -1
  213. package/dist/tools/FileReadTool/prompt.js.map +2 -2
  214. package/dist/tools/FileWriteTool/FileWriteTool.js +1 -1
  215. package/dist/tools/FileWriteTool/FileWriteTool.js.map +1 -1
  216. package/dist/tools/FileWriteTool/prompt.js +12 -3
  217. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  218. package/dist/tools/GlobTool/prompt.js +12 -1
  219. package/dist/tools/GlobTool/prompt.js.map +2 -2
  220. package/dist/tools/GrepTool/GrepTool.js +333 -65
  221. package/dist/tools/GrepTool/GrepTool.js.map +2 -2
  222. package/dist/tools/GrepTool/prompt.js +15 -8
  223. package/dist/tools/GrepTool/prompt.js.map +2 -2
  224. package/dist/tools/MultiEditTool/prompt.js +5 -3
  225. package/dist/tools/MultiEditTool/prompt.js.map +2 -2
  226. package/dist/tools/NotebookEditTool/NotebookEditTool.js +59 -46
  227. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  228. package/dist/tools/NotebookEditTool/prompt.js +1 -1
  229. package/dist/tools/NotebookEditTool/prompt.js.map +1 -1
  230. package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
  231. package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
  232. package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
  233. package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
  234. package/dist/tools/PlanModeTool/prompt.js +1 -1
  235. package/dist/tools/PlanModeTool/prompt.js.map +1 -1
  236. package/dist/tools/SkillTool/SkillTool.js +4 -3
  237. package/dist/tools/SkillTool/SkillTool.js.map +2 -2
  238. package/dist/tools/SkillTool/prompt.js +1 -1
  239. package/dist/tools/SkillTool/prompt.js.map +1 -1
  240. package/dist/tools/TaskCreateTool/TaskCreateTool.js +102 -0
  241. package/dist/tools/TaskCreateTool/TaskCreateTool.js.map +7 -0
  242. package/dist/tools/TaskCreateTool/prompt.js +47 -0
  243. package/dist/tools/TaskCreateTool/prompt.js.map +7 -0
  244. package/dist/tools/TaskGetTool/TaskGetTool.js +115 -0
  245. package/dist/tools/TaskGetTool/TaskGetTool.js.map +7 -0
  246. package/dist/tools/TaskGetTool/prompt.js +28 -0
  247. package/dist/tools/TaskGetTool/prompt.js.map +7 -0
  248. package/dist/tools/TaskListTool/TaskListTool.js +102 -0
  249. package/dist/tools/TaskListTool/TaskListTool.js.map +7 -0
  250. package/dist/tools/TaskListTool/prompt.js +27 -0
  251. package/dist/tools/TaskListTool/prompt.js.map +7 -0
  252. package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
  253. package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
  254. package/dist/tools/TaskStopTool/TaskStopTool.js +150 -0
  255. package/dist/tools/TaskStopTool/TaskStopTool.js.map +7 -0
  256. package/dist/tools/TaskStopTool/prompt.js +15 -0
  257. package/dist/tools/TaskStopTool/prompt.js.map +7 -0
  258. package/dist/tools/TaskTool/TaskTool.js +49 -1
  259. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  260. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js +134 -0
  261. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js.map +7 -0
  262. package/dist/tools/TaskUpdateTool/prompt.js +81 -0
  263. package/dist/tools/TaskUpdateTool/prompt.js.map +7 -0
  264. package/dist/tools/URLFetcherTool/prompt.js +1 -1
  265. package/dist/tools/URLFetcherTool/prompt.js.map +1 -1
  266. package/dist/tools.js +12 -0
  267. package/dist/tools.js.map +2 -2
  268. package/dist/utils/CircuitBreaker.js +242 -0
  269. package/dist/utils/CircuitBreaker.js.map +7 -0
  270. package/dist/utils/ask.js +2 -0
  271. package/dist/utils/ask.js.map +2 -2
  272. package/dist/utils/config.js +47 -5
  273. package/dist/utils/config.js.map +2 -2
  274. package/dist/utils/credentials/CredentialStore.js +1 -0
  275. package/dist/utils/credentials/CredentialStore.js.map +7 -0
  276. package/dist/utils/credentials/EncryptedFileStore.js +157 -0
  277. package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
  278. package/dist/utils/credentials/index.js +37 -0
  279. package/dist/utils/credentials/index.js.map +7 -0
  280. package/dist/utils/credentials/migration.js +82 -0
  281. package/dist/utils/credentials/migration.js.map +7 -0
  282. package/dist/utils/markdown.js +13 -1
  283. package/dist/utils/markdown.js.map +2 -2
  284. package/dist/utils/model.js +15 -2
  285. package/dist/utils/model.js.map +2 -2
  286. package/dist/utils/permissions/filesystem.js +5 -1
  287. package/dist/utils/permissions/filesystem.js.map +2 -2
  288. package/dist/utils/ripgrep.js +53 -1
  289. package/dist/utils/ripgrep.js.map +2 -2
  290. package/dist/utils/safePath.js +132 -0
  291. package/dist/utils/safePath.js.map +7 -0
  292. package/dist/utils/sensitiveFiles.js +125 -0
  293. package/dist/utils/sensitiveFiles.js.map +7 -0
  294. package/dist/utils/taskDisplayUtils.js +9 -9
  295. package/dist/utils/taskDisplayUtils.js.map +2 -2
  296. package/dist/utils/terminal.js +12 -0
  297. package/dist/utils/terminal.js.map +2 -2
  298. package/dist/utils/theme.js +6 -6
  299. package/dist/utils/theme.js.map +1 -1
  300. package/dist/utils/toolRiskClassification.js +207 -0
  301. package/dist/utils/toolRiskClassification.js.map +7 -0
  302. package/dist/utils/tooling/safeRender.js +17 -17
  303. package/dist/utils/tooling/safeRender.js.map +2 -2
  304. package/dist/version.js +2 -2
  305. package/dist/version.js.map +1 -1
  306. package/package.json +22 -28
  307. package/dist/hooks/useCancelRequest.js +0 -31
  308. 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
@@ -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;AAgBlC,eAAsB,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;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,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;",
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
  }
@@ -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
- writeFileSync(file, JSON.stringify(filteredConfig, null, 2), "utf-8");
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,