@within-7/minto 0.2.0 → 0.3.0

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 (254) 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 +7 -4
  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/toolInputExamples.js +84 -0
  140. package/dist/constants/toolInputExamples.js.map +7 -0
  141. package/dist/core/backupManager.js +321 -0
  142. package/dist/core/backupManager.js.map +7 -0
  143. package/dist/core/costTracker.js +9 -18
  144. package/dist/core/costTracker.js.map +2 -2
  145. package/dist/core/gitAutoCommit.js +287 -0
  146. package/dist/core/gitAutoCommit.js.map +7 -0
  147. package/dist/core/index.js +3 -0
  148. package/dist/core/index.js.map +2 -2
  149. package/dist/core/operationTracker.js +212 -0
  150. package/dist/core/operationTracker.js.map +7 -0
  151. package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
  152. package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
  153. package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
  154. package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
  155. package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
  156. package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
  157. package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
  158. package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
  159. package/dist/core/tokenStats.js +9 -0
  160. package/dist/core/tokenStats.js.map +7 -0
  161. package/dist/core/tokenStatsManager.js +331 -0
  162. package/dist/core/tokenStatsManager.js.map +7 -0
  163. package/dist/entrypoints/cli.js +115 -87
  164. package/dist/entrypoints/cli.js.map +2 -2
  165. package/dist/hooks/useAgentTokenStats.js +72 -0
  166. package/dist/hooks/useAgentTokenStats.js.map +7 -0
  167. package/dist/hooks/useAgentTranscripts.js +30 -6
  168. package/dist/hooks/useAgentTranscripts.js.map +2 -2
  169. package/dist/hooks/useLogMessages.js +12 -1
  170. package/dist/hooks/useLogMessages.js.map +2 -2
  171. package/dist/i18n/locales/en.js +6 -5
  172. package/dist/i18n/locales/en.js.map +2 -2
  173. package/dist/i18n/locales/zh-CN.js +6 -5
  174. package/dist/i18n/locales/zh-CN.js.map +2 -2
  175. package/dist/i18n/types.js.map +1 -1
  176. package/dist/permissions.js +28 -1
  177. package/dist/permissions.js.map +2 -2
  178. package/dist/query.js +78 -4
  179. package/dist/query.js.map +3 -3
  180. package/dist/screens/REPL.js +23 -3
  181. package/dist/screens/REPL.js.map +2 -2
  182. package/dist/services/claude.js +54 -3
  183. package/dist/services/claude.js.map +2 -2
  184. package/dist/services/intelligentCompactor.js +1 -1
  185. package/dist/services/intelligentCompactor.js.map +2 -2
  186. package/dist/services/mcpClient.js +81 -25
  187. package/dist/services/mcpClient.js.map +2 -2
  188. package/dist/services/sandbox/filesystemBoundary.js +58 -17
  189. package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
  190. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
  191. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
  192. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +2 -1
  193. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  194. package/dist/tools/BashTool/BashTool.js +22 -3
  195. package/dist/tools/BashTool/BashTool.js.map +2 -2
  196. package/dist/tools/BashTool/prompt.js +178 -34
  197. package/dist/tools/BashTool/prompt.js.map +2 -2
  198. package/dist/tools/FileEditTool/prompt.js +6 -3
  199. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  200. package/dist/tools/FileWriteTool/prompt.js +4 -2
  201. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  202. package/dist/tools/MultiEditTool/prompt.js +5 -3
  203. package/dist/tools/MultiEditTool/prompt.js.map +2 -2
  204. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -1
  205. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  206. package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
  207. package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
  208. package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
  209. package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
  210. package/dist/tools/PlanModeTool/prompt.js +1 -1
  211. package/dist/tools/PlanModeTool/prompt.js.map +1 -1
  212. package/dist/tools/SkillTool/SkillTool.js +4 -3
  213. package/dist/tools/SkillTool/SkillTool.js.map +2 -2
  214. package/dist/tools/SkillTool/prompt.js +1 -1
  215. package/dist/tools/SkillTool/prompt.js.map +1 -1
  216. package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
  217. package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
  218. package/dist/tools/TaskTool/TaskTool.js +8 -0
  219. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  220. package/dist/utils/CircuitBreaker.js +242 -0
  221. package/dist/utils/CircuitBreaker.js.map +7 -0
  222. package/dist/utils/ask.js +2 -0
  223. package/dist/utils/ask.js.map +2 -2
  224. package/dist/utils/config.js +47 -5
  225. package/dist/utils/config.js.map +2 -2
  226. package/dist/utils/credentials/CredentialStore.js +1 -0
  227. package/dist/utils/credentials/CredentialStore.js.map +7 -0
  228. package/dist/utils/credentials/EncryptedFileStore.js +157 -0
  229. package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
  230. package/dist/utils/credentials/index.js +37 -0
  231. package/dist/utils/credentials/index.js.map +7 -0
  232. package/dist/utils/credentials/migration.js +82 -0
  233. package/dist/utils/credentials/migration.js.map +7 -0
  234. package/dist/utils/markdown.js +13 -1
  235. package/dist/utils/markdown.js.map +2 -2
  236. package/dist/utils/permissions/filesystem.js +5 -1
  237. package/dist/utils/permissions/filesystem.js.map +2 -2
  238. package/dist/utils/safePath.js +132 -0
  239. package/dist/utils/safePath.js.map +7 -0
  240. package/dist/utils/sensitiveFiles.js +125 -0
  241. package/dist/utils/sensitiveFiles.js.map +7 -0
  242. package/dist/utils/taskDisplayUtils.js +9 -9
  243. package/dist/utils/taskDisplayUtils.js.map +2 -2
  244. package/dist/utils/theme.js +6 -6
  245. package/dist/utils/theme.js.map +1 -1
  246. package/dist/utils/toolRiskClassification.js +207 -0
  247. package/dist/utils/toolRiskClassification.js.map +7 -0
  248. package/dist/utils/tooling/safeRender.js +5 -4
  249. package/dist/utils/tooling/safeRender.js.map +2 -2
  250. package/dist/version.js +2 -2
  251. package/dist/version.js.map +1 -1
  252. package/package.json +9 -7
  253. package/dist/hooks/useCancelRequest.js +0 -31
  254. package/dist/hooks/useCancelRequest.js.map +0 -7
@@ -1,5 +1,6 @@
1
1
  import { resolve, relative, isAbsolute, dirname } from "path";
2
2
  import { minimatch } from "minimatch";
3
+ import { safeResolvePath } from "../../utils/safePath.js";
3
4
  class FilesystemBoundary {
4
5
  policy;
5
6
  workingDir;
@@ -24,7 +25,20 @@ class FilesystemBoundary {
24
25
  * Check if a path can be read
25
26
  */
26
27
  canRead(path) {
27
- const absolutePath = this.resolvePath(path);
28
+ const validationResult = safeResolvePath(
29
+ path,
30
+ this.workingDir,
31
+ this.workingDir
32
+ );
33
+ const absolutePath = validationResult.resolvedPath;
34
+ if (!validationResult.valid) {
35
+ const violation2 = this.createViolation(
36
+ "filesystem_path_denied",
37
+ `Symlink traversal detected: ${validationResult.error}`,
38
+ absolutePath
39
+ );
40
+ return { allowed: false, violation: violation2, isSymlink: true };
41
+ }
28
42
  const relativePath = this.getRelativePath(absolutePath);
29
43
  if (this.matchesPattern(relativePath, this.policy.denied)) {
30
44
  const violation2 = this.createViolation(
@@ -32,29 +46,46 @@ class FilesystemBoundary {
32
46
  `Read access denied for path matching deny pattern: ${path}`,
33
47
  absolutePath
34
48
  );
35
- return { allowed: false, violation: violation2 };
49
+ return {
50
+ allowed: false,
51
+ violation: violation2,
52
+ isSymlink: validationResult.isSymlink
53
+ };
36
54
  }
37
55
  if (this.matchesPattern(relativePath, this.policy.readAllowed)) {
38
- return { allowed: true };
56
+ return { allowed: true, isSymlink: validationResult.isSymlink };
39
57
  }
40
58
  if (this.matchesPattern(absolutePath, this.policy.readAllowed)) {
41
- return { allowed: true };
59
+ return { allowed: true, isSymlink: validationResult.isSymlink };
42
60
  }
43
61
  if (this.policy.readAllowed.includes("*")) {
44
- return { allowed: true };
62
+ return { allowed: true, isSymlink: validationResult.isSymlink };
45
63
  }
46
64
  const violation = this.createViolation(
47
65
  "filesystem_read_denied",
48
66
  `Read access denied: path does not match any allowed pattern: ${path}`,
49
67
  absolutePath
50
68
  );
51
- return { allowed: false, violation };
69
+ return { allowed: false, violation, isSymlink: validationResult.isSymlink };
52
70
  }
53
71
  /**
54
72
  * Check if a path can be written
55
73
  */
56
74
  canWrite(path) {
57
- const absolutePath = this.resolvePath(path);
75
+ const validationResult = safeResolvePath(
76
+ path,
77
+ this.workingDir,
78
+ this.workingDir
79
+ );
80
+ const absolutePath = validationResult.resolvedPath;
81
+ if (!validationResult.valid) {
82
+ const violation2 = this.createViolation(
83
+ "filesystem_path_denied",
84
+ `Symlink traversal detected: ${validationResult.error}`,
85
+ absolutePath
86
+ );
87
+ return { allowed: false, violation: violation2, isSymlink: true };
88
+ }
58
89
  const relativePath = this.getRelativePath(absolutePath);
59
90
  if (this.matchesPattern(relativePath, this.policy.denied)) {
60
91
  const violation2 = this.createViolation(
@@ -62,20 +93,24 @@ class FilesystemBoundary {
62
93
  `Write access denied for path matching deny pattern: ${path}`,
63
94
  absolutePath
64
95
  );
65
- return { allowed: false, violation: violation2 };
96
+ return {
97
+ allowed: false,
98
+ violation: violation2,
99
+ isSymlink: validationResult.isSymlink
100
+ };
66
101
  }
67
102
  if (this.matchesPattern(relativePath, this.policy.writeAllowed)) {
68
- return { allowed: true };
103
+ return { allowed: true, isSymlink: validationResult.isSymlink };
69
104
  }
70
105
  if (this.matchesPattern(absolutePath, this.policy.writeAllowed)) {
71
- return { allowed: true };
106
+ return { allowed: true, isSymlink: validationResult.isSymlink };
72
107
  }
73
108
  if (this.policy.writeAllowed.includes("*")) {
74
- return { allowed: true };
109
+ return { allowed: true, isSymlink: validationResult.isSymlink };
75
110
  }
76
111
  if (this.isUnderWorkingDir(absolutePath)) {
77
112
  if (this.policy.writeAllowed.includes("./")) {
78
- return { allowed: true };
113
+ return { allowed: true, isSymlink: validationResult.isSymlink };
79
114
  }
80
115
  }
81
116
  const violation = this.createViolation(
@@ -83,7 +118,7 @@ class FilesystemBoundary {
83
118
  `Write access denied: path does not match any allowed pattern: ${path}`,
84
119
  absolutePath
85
120
  );
86
- return { allowed: false, violation };
121
+ return { allowed: false, violation, isSymlink: validationResult.isSymlink };
87
122
  }
88
123
  /**
89
124
  * Analyze a command for filesystem operations and validate them
@@ -235,13 +270,19 @@ class FilesystemBoundary {
235
270
  return str.includes("/") || str.startsWith(".") || str.endsWith(".txt") || str.endsWith(".json") || str.endsWith(".js") || str.endsWith(".ts") || str.endsWith(".md");
236
271
  }
237
272
  /**
238
- * Resolve a path to absolute
273
+ * Resolve a path to absolute, following symlinks and validating boundaries.
274
+ * Uses safe resolution to prevent symlink traversal attacks.
239
275
  */
240
276
  resolvePath(path) {
241
- if (isAbsolute(path)) {
242
- return path;
277
+ const validationResult = safeResolvePath(
278
+ path,
279
+ this.workingDir,
280
+ this.workingDir
281
+ );
282
+ if (!validationResult.valid) {
283
+ return validationResult.resolvedPath;
243
284
  }
244
- return resolve(this.workingDir, path);
285
+ return validationResult.resolvedPath;
245
286
  }
246
287
  /**
247
288
  * Get path relative to working directory
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/services/sandbox/filesystemBoundary.ts"],
4
- "sourcesContent": ["/**\n * Filesystem Boundary Service\n *\n * Validates file system access against configured policies.\n * Provides path validation and access control for sandboxed execution.\n */\n\nimport { resolve, relative, isAbsolute, dirname } from 'path'\nimport { minimatch } from 'minimatch'\nimport type { FilesystemPolicy, SandboxViolation } from './types'\n\n/**\n * Result of a filesystem access check\n */\nexport interface FilesystemCheckResult {\n allowed: boolean\n violation?: SandboxViolation\n}\n\n/**\n * Filesystem Boundary Validator\n */\nexport class FilesystemBoundary {\n private policy: FilesystemPolicy\n private workingDir: string\n private violations: SandboxViolation[] = []\n\n constructor(policy: FilesystemPolicy, workingDir: string) {\n this.policy = policy\n this.workingDir = resolve(workingDir)\n }\n\n /**\n * Update the filesystem policy\n */\n updatePolicy(policy: Partial<FilesystemPolicy>): void {\n this.policy = { ...this.policy, ...policy }\n }\n\n /**\n * Update the working directory\n */\n setWorkingDir(dir: string): void {\n this.workingDir = resolve(dir)\n }\n\n /**\n * Check if a path can be read\n */\n canRead(path: string): FilesystemCheckResult {\n const absolutePath = this.resolvePath(path)\n const relativePath = this.getRelativePath(absolutePath)\n\n // Check denied patterns first (highest priority)\n if (this.matchesPattern(relativePath, this.policy.denied)) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Read access denied for path matching deny pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation }\n }\n\n // Check if path matches read-allowed patterns\n if (this.matchesPattern(relativePath, this.policy.readAllowed)) {\n return { allowed: true }\n }\n\n // Also check with absolute path\n if (this.matchesPattern(absolutePath, this.policy.readAllowed)) {\n return { allowed: true }\n }\n\n // Wildcard allows all reads\n if (this.policy.readAllowed.includes('*')) {\n return { allowed: true }\n }\n\n const violation = this.createViolation(\n 'filesystem_read_denied',\n `Read access denied: path does not match any allowed pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation }\n }\n\n /**\n * Check if a path can be written\n */\n canWrite(path: string): FilesystemCheckResult {\n const absolutePath = this.resolvePath(path)\n const relativePath = this.getRelativePath(absolutePath)\n\n // Check denied patterns first (highest priority)\n if (this.matchesPattern(relativePath, this.policy.denied)) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Write access denied for path matching deny pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation }\n }\n\n // Check if path matches write-allowed patterns\n if (this.matchesPattern(relativePath, this.policy.writeAllowed)) {\n return { allowed: true }\n }\n\n // Also check with absolute path\n if (this.matchesPattern(absolutePath, this.policy.writeAllowed)) {\n return { allowed: true }\n }\n\n // Wildcard allows all writes\n if (this.policy.writeAllowed.includes('*')) {\n return { allowed: true }\n }\n\n // Check if path is under working directory (default write location)\n if (this.isUnderWorkingDir(absolutePath)) {\n // Only allow if './' is in writeAllowed\n if (this.policy.writeAllowed.includes('./')) {\n return { allowed: true }\n }\n }\n\n const violation = this.createViolation(\n 'filesystem_write_denied',\n `Write access denied: path does not match any allowed pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation }\n }\n\n /**\n * Analyze a command for filesystem operations and validate them\n */\n analyzeCommand(command: string): {\n readPaths: string[]\n writePaths: string[]\n violations: SandboxViolation[]\n } {\n const readPaths: string[] = []\n const writePaths: string[] = []\n const violations: SandboxViolation[] = []\n\n // Extract potential paths from command\n const extractedPaths = this.extractPathsFromCommand(command)\n\n // Detect redirect write operations first (highest priority)\n // Match patterns like: command > file, command >> file\n const redirectMatches = command.matchAll(/>\\s*[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of redirectMatches) {\n if (match[1] && !match[1].startsWith('&')) {\n writePaths.push(match[1])\n }\n }\n\n // Detect write operations with specific commands\n // tee command\n const teeMatches = command.matchAll(\n /\\btee\\s+(?:-a\\s+)?[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of teeMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // cp command - target is the last argument\n const cpMatches = command.matchAll(\n /\\bcp\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of cpMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // mv command - target is the last argument\n const mvMatches = command.matchAll(\n /\\bmv\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of mvMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // rm command\n const rmMatches = command.matchAll(\n /\\brm\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of rmMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // mkdir command\n const mkdirMatches = command.matchAll(\n /\\bmkdir\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of mkdirMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // rmdir command\n const rmdirMatches = command.matchAll(\n /\\brmdir\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of rmdirMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // touch command\n const touchMatches = command.matchAll(/\\btouch\\s+[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of touchMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // chmod command - target is the last argument\n const chmodMatches = command.matchAll(\n /\\bchmod\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of chmodMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // chown command - target is the last argument\n const chownMatches = command.matchAll(\n /\\bchown\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of chownMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // Detect read operations\n // cat command (when not redirecting)\n if (!command.includes('>')) {\n const catMatches = command.matchAll(/\\bcat\\s+[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of catMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n }\n\n // head/tail commands\n const headTailMatches = command.matchAll(\n /\\b(?:head|tail)\\s+(?:-[a-zA-Z0-9]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of headTailMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n\n // less/more commands\n const pagerMatches = command.matchAll(\n /\\b(?:less|more)\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of pagerMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n\n // Add extracted paths that might be read (if not already in writePaths)\n for (const path of extractedPaths) {\n if (!writePaths.includes(path) && !readPaths.includes(path)) {\n readPaths.push(path)\n }\n }\n\n // Deduplicate paths\n const uniqueWritePaths = [...new Set(writePaths)]\n const uniqueReadPaths = [...new Set(readPaths)]\n\n // Validate all paths\n for (const path of uniqueReadPaths) {\n const result = this.canRead(path)\n if (!result.allowed && result.violation) {\n violations.push(result.violation)\n }\n }\n\n for (const path of uniqueWritePaths) {\n const result = this.canWrite(path)\n if (!result.allowed && result.violation) {\n violations.push(result.violation)\n }\n }\n\n // Store violations\n this.violations.push(...violations)\n\n return {\n readPaths: uniqueReadPaths,\n writePaths: uniqueWritePaths,\n violations,\n }\n }\n\n /**\n * Get all recorded violations\n */\n getViolations(): SandboxViolation[] {\n return [...this.violations]\n }\n\n /**\n * Clear violation history\n */\n clearViolations(): void {\n this.violations = []\n }\n\n /**\n * Extract potential file paths from a command string\n */\n private extractPathsFromCommand(command: string): string[] {\n const paths: string[] = []\n\n // Match quoted strings\n const quotedMatches = command.matchAll(/[\"']([^\"']+)[\"']/g)\n for (const match of quotedMatches) {\n if (match[1] && this.looksLikePath(match[1])) {\n paths.push(match[1])\n }\n }\n\n // Match unquoted paths (starting with ./ or / or containing /)\n const unquotedMatches = command.matchAll(\n /(?:^|\\s)((?:\\.\\/|\\/|\\.\\.\\/)[^\\s;&|>]+)/g,\n )\n for (const match of unquotedMatches) {\n if (match[1]) {\n paths.push(match[1])\n }\n }\n\n return [...new Set(paths)] // Deduplicate\n }\n\n /**\n * Check if a string looks like a file path\n */\n private looksLikePath(str: string): boolean {\n return (\n str.includes('/') ||\n str.startsWith('.') ||\n str.endsWith('.txt') ||\n str.endsWith('.json') ||\n str.endsWith('.js') ||\n str.endsWith('.ts') ||\n str.endsWith('.md')\n )\n }\n\n /**\n * Resolve a path to absolute\n */\n private resolvePath(path: string): string {\n if (isAbsolute(path)) {\n return path\n }\n return resolve(this.workingDir, path)\n }\n\n /**\n * Get path relative to working directory\n */\n private getRelativePath(absolutePath: string): string {\n return relative(this.workingDir, absolutePath)\n }\n\n /**\n * Check if a path is under the working directory\n */\n private isUnderWorkingDir(absolutePath: string): boolean {\n const rel = relative(this.workingDir, absolutePath)\n return !rel.startsWith('..') && !isAbsolute(rel)\n }\n\n /**\n * Check if a path matches any of the given patterns\n */\n private matchesPattern(path: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n // Handle special '*' pattern for everything\n if (pattern === '*') {\n return true\n }\n\n // Handle './' for current directory\n if (pattern === './') {\n if (this.isUnderWorkingDir(this.resolvePath(path))) {\n return true\n }\n continue\n }\n\n // Use minimatch for glob patterns\n if (\n minimatch(path, pattern, { dot: true }) ||\n minimatch(path, `**/${pattern}`, { dot: true })\n ) {\n return true\n }\n\n // Check if pattern matches directory\n const pathDir = dirname(path)\n if (minimatch(pathDir, pattern, { dot: true })) {\n return true\n }\n }\n return false\n }\n\n /**\n * Create a violation record\n */\n private createViolation(\n type: SandboxViolation['type'],\n details: string,\n path: string,\n ): SandboxViolation {\n return {\n type,\n timestamp: Date.now(),\n command: '', // Will be set by caller\n details,\n path,\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Filesystem Boundary Service\n *\n * Validates file system access against configured policies.\n * Provides path validation and access control for sandboxed execution.\n */\n\nimport { resolve, relative, isAbsolute, dirname } from 'path'\nimport { minimatch } from 'minimatch'\nimport { safeResolvePath } from '../../utils/safePath'\nimport type { FilesystemPolicy, SandboxViolation } from './types'\n\n/**\n * Result of a filesystem access check\n */\nexport interface FilesystemCheckResult {\n allowed: boolean\n violation?: SandboxViolation\n /**\n * Whether the path or any component in its chain is a symbolic link.\n * Set when symlink traversal protection detects symlinks.\n */\n isSymlink?: boolean\n}\n\n/**\n * Filesystem Boundary Validator\n */\nexport class FilesystemBoundary {\n private policy: FilesystemPolicy\n private workingDir: string\n private violations: SandboxViolation[] = []\n\n constructor(policy: FilesystemPolicy, workingDir: string) {\n this.policy = policy\n this.workingDir = resolve(workingDir)\n }\n\n /**\n * Update the filesystem policy\n */\n updatePolicy(policy: Partial<FilesystemPolicy>): void {\n this.policy = { ...this.policy, ...policy }\n }\n\n /**\n * Update the working directory\n */\n setWorkingDir(dir: string): void {\n this.workingDir = resolve(dir)\n }\n\n /**\n * Check if a path can be read\n */\n canRead(path: string): FilesystemCheckResult {\n // Use safe resolution to detect symlinks and validate boundaries\n const validationResult = safeResolvePath(\n path,\n this.workingDir,\n this.workingDir,\n )\n const absolutePath = validationResult.resolvedPath\n\n // If safe resolution detected a boundary escape, deny access\n if (!validationResult.valid) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Symlink traversal detected: ${validationResult.error}`,\n absolutePath,\n )\n return { allowed: false, violation, isSymlink: true }\n }\n\n const relativePath = this.getRelativePath(absolutePath)\n\n // Check denied patterns first (highest priority)\n if (this.matchesPattern(relativePath, this.policy.denied)) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Read access denied for path matching deny pattern: ${path}`,\n absolutePath,\n )\n return {\n allowed: false,\n violation,\n isSymlink: validationResult.isSymlink,\n }\n }\n\n // Check if path matches read-allowed patterns\n if (this.matchesPattern(relativePath, this.policy.readAllowed)) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n // Also check with absolute path\n if (this.matchesPattern(absolutePath, this.policy.readAllowed)) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n // Wildcard allows all reads\n if (this.policy.readAllowed.includes('*')) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n const violation = this.createViolation(\n 'filesystem_read_denied',\n `Read access denied: path does not match any allowed pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation, isSymlink: validationResult.isSymlink }\n }\n\n /**\n * Check if a path can be written\n */\n canWrite(path: string): FilesystemCheckResult {\n // Use safe resolution to detect symlinks and validate boundaries\n const validationResult = safeResolvePath(\n path,\n this.workingDir,\n this.workingDir,\n )\n const absolutePath = validationResult.resolvedPath\n\n // If safe resolution detected a boundary escape, deny access\n if (!validationResult.valid) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Symlink traversal detected: ${validationResult.error}`,\n absolutePath,\n )\n return { allowed: false, violation, isSymlink: true }\n }\n\n const relativePath = this.getRelativePath(absolutePath)\n\n // Check denied patterns first (highest priority)\n if (this.matchesPattern(relativePath, this.policy.denied)) {\n const violation = this.createViolation(\n 'filesystem_path_denied',\n `Write access denied for path matching deny pattern: ${path}`,\n absolutePath,\n )\n return {\n allowed: false,\n violation,\n isSymlink: validationResult.isSymlink,\n }\n }\n\n // Check if path matches write-allowed patterns\n if (this.matchesPattern(relativePath, this.policy.writeAllowed)) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n // Also check with absolute path\n if (this.matchesPattern(absolutePath, this.policy.writeAllowed)) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n // Wildcard allows all writes\n if (this.policy.writeAllowed.includes('*')) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n\n // Check if path is under working directory (default write location)\n if (this.isUnderWorkingDir(absolutePath)) {\n // Only allow if './' is in writeAllowed\n if (this.policy.writeAllowed.includes('./')) {\n return { allowed: true, isSymlink: validationResult.isSymlink }\n }\n }\n\n const violation = this.createViolation(\n 'filesystem_write_denied',\n `Write access denied: path does not match any allowed pattern: ${path}`,\n absolutePath,\n )\n return { allowed: false, violation, isSymlink: validationResult.isSymlink }\n }\n\n /**\n * Analyze a command for filesystem operations and validate them\n */\n analyzeCommand(command: string): {\n readPaths: string[]\n writePaths: string[]\n violations: SandboxViolation[]\n } {\n const readPaths: string[] = []\n const writePaths: string[] = []\n const violations: SandboxViolation[] = []\n\n // Extract potential paths from command\n const extractedPaths = this.extractPathsFromCommand(command)\n\n // Detect redirect write operations first (highest priority)\n // Match patterns like: command > file, command >> file\n const redirectMatches = command.matchAll(/>\\s*[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of redirectMatches) {\n if (match[1] && !match[1].startsWith('&')) {\n writePaths.push(match[1])\n }\n }\n\n // Detect write operations with specific commands\n // tee command\n const teeMatches = command.matchAll(\n /\\btee\\s+(?:-a\\s+)?[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of teeMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // cp command - target is the last argument\n const cpMatches = command.matchAll(\n /\\bcp\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of cpMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // mv command - target is the last argument\n const mvMatches = command.matchAll(\n /\\bmv\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of mvMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // rm command\n const rmMatches = command.matchAll(\n /\\brm\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of rmMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // mkdir command\n const mkdirMatches = command.matchAll(\n /\\bmkdir\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of mkdirMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // rmdir command\n const rmdirMatches = command.matchAll(\n /\\brmdir\\s+(?:-[a-zA-Z]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of rmdirMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // touch command\n const touchMatches = command.matchAll(/\\btouch\\s+[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of touchMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // chmod command - target is the last argument\n const chmodMatches = command.matchAll(\n /\\bchmod\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of chmodMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // chown command - target is the last argument\n const chownMatches = command.matchAll(\n /\\bchown\\s+(?:-[a-zA-Z]+\\s+)*\\S+\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of chownMatches) {\n if (match[1]) writePaths.push(match[1])\n }\n\n // Detect read operations\n // cat command (when not redirecting)\n if (!command.includes('>')) {\n const catMatches = command.matchAll(/\\bcat\\s+[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of catMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n }\n\n // head/tail commands\n const headTailMatches = command.matchAll(\n /\\b(?:head|tail)\\s+(?:-[a-zA-Z0-9]+\\s+)*[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of headTailMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n\n // less/more commands\n const pagerMatches = command.matchAll(\n /\\b(?:less|more)\\s+[\"']?([^\\s\"';&|]+)[\"']?/g,\n )\n for (const match of pagerMatches) {\n if (match[1]) readPaths.push(match[1])\n }\n\n // Add extracted paths that might be read (if not already in writePaths)\n for (const path of extractedPaths) {\n if (!writePaths.includes(path) && !readPaths.includes(path)) {\n readPaths.push(path)\n }\n }\n\n // Deduplicate paths\n const uniqueWritePaths = [...new Set(writePaths)]\n const uniqueReadPaths = [...new Set(readPaths)]\n\n // Validate all paths\n for (const path of uniqueReadPaths) {\n const result = this.canRead(path)\n if (!result.allowed && result.violation) {\n violations.push(result.violation)\n }\n }\n\n for (const path of uniqueWritePaths) {\n const result = this.canWrite(path)\n if (!result.allowed && result.violation) {\n violations.push(result.violation)\n }\n }\n\n // Store violations\n this.violations.push(...violations)\n\n return {\n readPaths: uniqueReadPaths,\n writePaths: uniqueWritePaths,\n violations,\n }\n }\n\n /**\n * Get all recorded violations\n */\n getViolations(): SandboxViolation[] {\n return [...this.violations]\n }\n\n /**\n * Clear violation history\n */\n clearViolations(): void {\n this.violations = []\n }\n\n /**\n * Extract potential file paths from a command string\n */\n private extractPathsFromCommand(command: string): string[] {\n const paths: string[] = []\n\n // Match quoted strings\n const quotedMatches = command.matchAll(/[\"']([^\"']+)[\"']/g)\n for (const match of quotedMatches) {\n if (match[1] && this.looksLikePath(match[1])) {\n paths.push(match[1])\n }\n }\n\n // Match unquoted paths (starting with ./ or / or containing /)\n const unquotedMatches = command.matchAll(\n /(?:^|\\s)((?:\\.\\/|\\/|\\.\\.\\/)[^\\s;&|>]+)/g,\n )\n for (const match of unquotedMatches) {\n if (match[1]) {\n paths.push(match[1])\n }\n }\n\n return [...new Set(paths)] // Deduplicate\n }\n\n /**\n * Check if a string looks like a file path\n */\n private looksLikePath(str: string): boolean {\n return (\n str.includes('/') ||\n str.startsWith('.') ||\n str.endsWith('.txt') ||\n str.endsWith('.json') ||\n str.endsWith('.js') ||\n str.endsWith('.ts') ||\n str.endsWith('.md')\n )\n }\n\n /**\n * Resolve a path to absolute, following symlinks and validating boundaries.\n * Uses safe resolution to prevent symlink traversal attacks.\n */\n private resolvePath(path: string): string {\n // Use safe path resolution to follow symlinks and validate boundaries\n const validationResult = safeResolvePath(\n path,\n this.workingDir,\n this.workingDir,\n )\n\n if (!validationResult.valid) {\n // If validation fails, still return the attempted path for error reporting\n // The caller (canRead/canWrite) will handle the violation\n return validationResult.resolvedPath\n }\n\n return validationResult.resolvedPath\n }\n\n /**\n * Get path relative to working directory\n */\n private getRelativePath(absolutePath: string): string {\n return relative(this.workingDir, absolutePath)\n }\n\n /**\n * Check if a path is under the working directory\n */\n private isUnderWorkingDir(absolutePath: string): boolean {\n const rel = relative(this.workingDir, absolutePath)\n return !rel.startsWith('..') && !isAbsolute(rel)\n }\n\n /**\n * Check if a path matches any of the given patterns\n */\n private matchesPattern(path: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n // Handle special '*' pattern for everything\n if (pattern === '*') {\n return true\n }\n\n // Handle './' for current directory\n if (pattern === './') {\n if (this.isUnderWorkingDir(this.resolvePath(path))) {\n return true\n }\n continue\n }\n\n // Use minimatch for glob patterns\n if (\n minimatch(path, pattern, { dot: true }) ||\n minimatch(path, `**/${pattern}`, { dot: true })\n ) {\n return true\n }\n\n // Check if pattern matches directory\n const pathDir = dirname(path)\n if (minimatch(pathDir, pattern, { dot: true })) {\n return true\n }\n }\n return false\n }\n\n /**\n * Create a violation record\n */\n private createViolation(\n type: SandboxViolation['type'],\n details: string,\n path: string,\n ): SandboxViolation {\n return {\n type,\n timestamp: Date.now(),\n command: '', // Will be set by caller\n details,\n path,\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["violation"]
7
7
  }
@@ -17,6 +17,7 @@ import {
17
17
  addMessageToSession
18
18
  } from "../../utils/expertChatStorage.js";
19
19
  import { queryLLM } from "../../services/claude.js";
20
+ import { SEMANTIC_COLORS } from "../../constants/colors.js";
20
21
  import { debug as debugLogger } from "../../utils/debugLogger.js";
21
22
  import { applyMarkdown } from "../../utils/markdown.js";
22
23
  const inputSchema = z.strictObject({
@@ -154,7 +155,7 @@ Question: What are the most effective React optimization techniques for handling
154
155
  if (verbose) {
155
156
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, expert_model), /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, sessionDisplay), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.text }, question.length > 300 ? question.substring(0, 300) + "..." : question)));
156
157
  }
157
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, expert_model, " "), /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText, dimColor: true }, "(", sessionDisplay, ")"));
158
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, expert_model, " "), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "(", sessionDisplay, ")"));
158
159
  },
159
160
  renderToolResultMessage(content) {
160
161
  const verbose = true;
@@ -173,7 +174,7 @@ Question: What are the most effective React optimization techniques for handling
173
174
  if (isError) {
174
175
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, answerText));
175
176
  }
176
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: theme.text }, "Response from ", expertResult.expertModelName, ":"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.text }, applyMarkdown(answerText))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText, dimColor: true }, "Session: ", expertResult.chatSessionId.substring(0, 8))));
177
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: theme.text }, "Response from ", expertResult.expertModelName, ":"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.text }, applyMarkdown(answerText))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Session: ", expertResult.chatSessionId.substring(0, 8))));
177
178
  }
178
179
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, { color: theme.secondaryText }, "Consultation completed"));
179
180
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/AskExpertModelTool/AskExpertModelTool.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { Box, Text } from 'ink'\nimport { z } from 'zod'\nimport { Tool, ValidationResult } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { getModelManager } from '@utils/model'\nimport { getTheme } from '@utils/theme'\nimport {\n createUserMessage,\n createAssistantMessage,\n INTERRUPT_MESSAGE,\n} from '@utils/messages'\nimport { logError } from '@utils/log'\nimport {\n createExpertChatSession,\n loadExpertChatSession,\n getSessionMessages,\n addMessageToSession,\n} from '@utils/expertChatStorage'\nimport { queryLLM } from '@services/claude'\nimport { debug as debugLogger } from '@utils/debugLogger'\nimport { applyMarkdown } from '@utils/markdown'\n\nexport const inputSchema = z.strictObject({\n question: z\n .string()\n .describe(\n 'COMPLETE SELF-CONTAINED QUESTION: Must include full background context, relevant details, and a clear independent question. The expert model will receive ONLY this content with no access to previous conversation or external context. Structure as: 1) Background/Context 2) Specific situation/problem 3) Clear question. Ensure the expert can fully understand and respond without needing additional information.',\n ),\n expert_model: z\n .string()\n .describe(\n 'The expert model to use (e.g., gpt-5, claude-3-5-sonnet-20241022)',\n ),\n chat_session_id: z\n .string()\n .describe(\n 'Chat session ID: use \"new\" for new session or existing session ID',\n ),\n})\n\ntype In = typeof inputSchema\nexport type Out = {\n chatSessionId: string\n expertModelName: string\n expertAnswer: string\n}\n\nexport const AskExpertModelTool = {\n name: 'AskExpertModel',\n async description() {\n return 'Consult external AI models for expert opinions and analysis'\n },\n async prompt() {\n return `Ask a question to a specific external AI model for expert analysis.\n\nThis tool allows you to consult different AI models for their unique perspectives and expertise.\n\nCRITICAL REQUIREMENT FOR QUESTION PARAMETER:\nThe question MUST be completely self-contained and include:\n1. FULL BACKGROUND CONTEXT - All relevant information the expert needs\n2. SPECIFIC SITUATION - Clear description of the current scenario/problem\n3. INDEPENDENT QUESTION - What exactly you want the expert to analyze/answer\n\nThe expert model receives ONLY your question content with NO access to:\n- Previous conversation history (unless using existing session) \n- Current codebase or file context\n- User's current task or project details\n\nIMPORTANT: This tool is for asking questions to models, not for task execution.\n- Use when you need a specific model's opinion or analysis\n- Use when you want to compare different models' responses\n- Use the @ask-[model] format when available\n\nThe expert_model parameter accepts:\n- OpenAI: gpt-4, gpt-5, o1-preview\n- Anthropic: claude-3-5-sonnet, claude-3-opus \n- Others: kimi, gemini-pro, mixtral\n\nExample of well-structured question:\n\"Background: I'm working on a React TypeScript application with performance issues. The app renders a large list of 10,000 items using a simple map() function, causing UI freezing.\n\nCurrent situation: Users report 3-5 second delays when scrolling through the list. The component re-renders the entire list on every state change.\n\nQuestion: What are the most effective React optimization techniques for handling large lists, and how should I prioritize implementing virtualization vs memoization vs other approaches?\"`\n },\n isReadOnly() {\n return true\n },\n isConcurrencySafe() {\n return true\n },\n inputSchema,\n userFacingName() {\n return 'AskExpertModel'\n },\n async isEnabled() {\n return true\n },\n needsPermissions(): boolean {\n return false\n },\n async validateInput(\n { question, expert_model, chat_session_id },\n context?: any,\n ): Promise<ValidationResult> {\n if (!question.trim()) {\n return { result: false, message: 'Question cannot be empty' }\n }\n\n if (!expert_model.trim()) {\n return { result: false, message: 'Expert model must be specified' }\n }\n\n if (!chat_session_id.trim()) {\n return {\n result: false,\n message:\n 'Chat session ID must be specified (use \"new\" for new session)',\n }\n }\n\n // Check if trying to consult the same model we're currently running\n try {\n const modelManager = getModelManager()\n\n // Get current model based on context\n let currentModel: string\n if (context?.agentId && context?.options?.model) {\n // In subagent context (Task tool)\n currentModel = context.options.model\n } else {\n // In main agent context or after model switch\n currentModel = modelManager.getModelName('main') || ''\n }\n\n // Normalize model names for comparison\n const normalizedExpert = expert_model\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n const normalizedCurrent = currentModel\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n\n if (normalizedExpert === normalizedCurrent) {\n return {\n result: false,\n message: `You are already running as ${currentModel}. Consulting the same model would be redundant. Please choose a different model or handle the task directly.`,\n }\n }\n } catch (e) {\n // If we can't determine current model, allow the request\n debugLogger.error('AskExpertModel', {\n message: 'Could not determine current model',\n error: e,\n })\n }\n\n // Validate that the model exists and is available\n try {\n const modelManager = getModelManager()\n const modelResolution = modelManager.resolveModelWithInfo(expert_model)\n\n if (!modelResolution.success) {\n const availableModels = modelManager.getAllAvailableModelNames()\n if (availableModels.length > 0) {\n return {\n result: false,\n message: `Model '${expert_model}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expert_model}' needs to be configured using /model command.`,\n }\n } else {\n return {\n result: false,\n message: `Model '${expert_model}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,\n }\n }\n }\n } catch (error) {\n console.error('Model validation error in AskExpertModelTool:', error)\n logError(error)\n return {\n result: false,\n message: `Failed to validate expert model '${expert_model}'. Please check your model configuration.`,\n }\n }\n\n return { result: true }\n },\n\n renderToolUseMessage(\n { question, expert_model, chat_session_id },\n { verbose },\n ) {\n if (!question || !expert_model) return null\n const isNewSession = chat_session_id === 'new'\n const sessionDisplay = isNewSession\n ? 'new session'\n : `session ${chat_session_id.substring(0, 5)}...`\n const theme = getTheme()\n\n if (verbose) {\n return (\n <Box flexDirection=\"column\">\n <Text bold color=\"yellow\">\n {expert_model}\n </Text>\n <Text color={theme.secondaryText}>{sessionDisplay}</Text>\n <Box marginTop={1}>\n <Text color={theme.text}>\n {question.length > 300\n ? question.substring(0, 300) + '...'\n : question}\n </Text>\n </Box>\n </Box>\n )\n }\n return (\n <Box flexDirection=\"column\">\n <Text bold color=\"yellow\">\n {expert_model}{' '}\n </Text>\n <Text color={theme.secondaryText} dimColor>\n ({sessionDisplay})\n </Text>\n </Box>\n )\n },\n\n renderToolResultMessage(content) {\n const verbose = true // Show more content\n const theme = getTheme()\n\n // Guard against undefined or null content\n if (!content) {\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation completed</Text>\n </Box>\n )\n }\n\n if (typeof content === 'object' && content && 'expertAnswer' in content) {\n const expertResult = content as Out\n const isError =\n expertResult.expertAnswer.startsWith('Error') ||\n expertResult.expertAnswer.includes('failed')\n const isInterrupted = expertResult.chatSessionId === 'interrupted'\n\n if (isInterrupted) {\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation interrupted</Text>\n </Box>\n )\n }\n\n const answerText = verbose\n ? expertResult.expertAnswer.trim()\n : expertResult.expertAnswer.length > 500\n ? expertResult.expertAnswer.substring(0, 500) + '...'\n : expertResult.expertAnswer.trim()\n\n if (isError) {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\">{answerText}</Text>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text bold color={theme.text}>\n Response from {expertResult.expertModelName}:\n </Text>\n <Box marginTop={1}>\n <Text color={theme.text}>{applyMarkdown(answerText)}</Text>\n </Box>\n <Box marginTop={1}>\n <Text color={theme.secondaryText} dimColor>\n Session: {expertResult.chatSessionId.substring(0, 8)}\n </Text>\n </Box>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation completed</Text>\n </Box>\n )\n },\n\n renderResultForAssistant(output: Out): string {\n return `[Expert consultation completed]\nExpert Model: ${output.expertModelName}\nSession ID: ${output.chatSessionId}\nTo continue this conversation with context preservation, use this Session ID in your next AskExpertModel call to maintain the full conversation history and context.\n\n${output.expertAnswer}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n async *call(\n { question, expert_model, chat_session_id },\n { abortController, readFileTimestamps },\n ) {\n const expertModel = expert_model\n\n let sessionId: string\n let isInterrupted = false\n\n // Set up abort listener (following TaskTool pattern)\n const abortListener = () => {\n isInterrupted = true\n }\n abortController.signal.addEventListener('abort', abortListener)\n\n try {\n // Initial abort check\n if (abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n // Session management with error handling\n if (chat_session_id === 'new') {\n try {\n const session = createExpertChatSession(expertModel)\n sessionId = session.sessionId\n } catch (error) {\n console.error('Failed to create new expert chat session:', error)\n logError(error)\n throw new Error('Failed to create new chat session')\n }\n } else {\n sessionId = chat_session_id\n try {\n const session = loadExpertChatSession(sessionId)\n if (!session) {\n // Session doesn't exist, create new one\n const newSession = createExpertChatSession(expertModel)\n sessionId = newSession.sessionId\n }\n } catch (error) {\n console.error('Failed to load expert chat session:', error)\n logError(error)\n // Fallback: create new session\n try {\n const newSession = createExpertChatSession(expertModel)\n sessionId = newSession.sessionId\n } catch (createError) {\n console.error(\n 'Failed to create fallback expert chat session:',\n createError,\n )\n logError(createError)\n throw new Error('Unable to create or load chat session')\n }\n }\n }\n\n // Check for cancellation before loading history\n if (isInterrupted || abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n\n // Load history and prepare messages with error handling\n let historyMessages: Array<{ role: string; content: string }>\n try {\n historyMessages = getSessionMessages(sessionId)\n } catch (error) {\n console.error('Failed to load session messages:', error)\n logError(error)\n historyMessages = [] // Fallback to empty history\n }\n\n const messages = [...historyMessages, { role: 'user', content: question }]\n\n let systemMessages\n try {\n systemMessages = messages.map(msg =>\n msg.role === 'user'\n ? createUserMessage(msg.content)\n : createAssistantMessage(msg.content),\n )\n } catch (error) {\n console.error('Failed to create system messages:', error)\n logError(error)\n throw new Error('Failed to prepare conversation messages')\n }\n\n // Check for cancellation before model call\n if (isInterrupted || abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n\n // Yield progress message to show we're connecting\n yield {\n type: 'progress',\n content: createAssistantMessage(\n `Connecting to ${expertModel}... (timeout: 5 minutes)`,\n ),\n }\n\n // Call model with comprehensive error handling and timeout\n let response\n try {\n // Debug: Log the model we're trying to use (using global debug logger)\n const modelManager = getModelManager()\n const modelResolution = modelManager.resolveModelWithInfo(expertModel)\n\n debugLogger.api('EXPERT_MODEL_RESOLUTION', {\n requestedModel: expertModel,\n success: modelResolution.success,\n profileName: modelResolution.profile?.name,\n profileModelName: modelResolution.profile?.modelName,\n provider: modelResolution.profile?.provider,\n isActive: modelResolution.profile?.isActive,\n error: modelResolution.error,\n })\n\n // Create a timeout promise to prevent hanging\n const timeoutMs = 300000 // 300 seconds (5 minutes) timeout for external models\n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `Expert model query timed out after ${timeoutMs / 1000}s`,\n ),\n )\n }, timeoutMs)\n })\n\n // Race between the query and timeout\n response = await Promise.race([\n queryLLM(\n systemMessages,\n [], // no system prompt - let expert model use its default behavior\n 0, // no thinking tokens needed\n [], // no tools needed\n abortController.signal,\n {\n safeMode: false,\n model: expertModel,\n prependCLISysprompt: false, // KEY: avoid injecting CLI context\n },\n ),\n timeoutPromise,\n ])\n } catch (error: any) {\n console.error('Expert model query failed:', error)\n logError(error)\n\n // Check for specific error types\n if (\n error.name === 'AbortError' ||\n abortController.signal?.aborted ||\n isInterrupted\n ) {\n return yield* this.handleInterrupt()\n }\n\n if (error.message?.includes('timed out')) {\n throw new Error(\n `Expert model '${expertModel}' timed out after 5 minutes.\\n\\n` +\n `Suggestions:\\n` +\n ` - The model might be experiencing high load\\n` +\n ` - Try a different model or retry later\\n` +\n ` - Consider breaking down your question into smaller parts`,\n )\n }\n\n if (error.message?.includes('rate limit')) {\n throw new Error(\n `Rate limit exceeded for ${expertModel}.\\n\\n` +\n `Please wait a moment and try again, or use a different model.`,\n )\n }\n\n if (error.message?.includes('invalid api key')) {\n throw new Error(\n `Invalid API key for ${expertModel}.\\n\\n` +\n `Please check your model configuration with /model command.`,\n )\n }\n\n if (\n error.message?.includes('model not found') ||\n error.message?.includes('Failed to resolve model')\n ) {\n // Provide helpful model guidance in runtime errors too\n try {\n const modelManager = getModelManager()\n const availableModels = modelManager.getAllAvailableModelNames()\n if (availableModels.length > 0) {\n throw new Error(\n `Model '${expertModel}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expertModel}' needs to be configured using /model command.`,\n )\n } else {\n throw new Error(\n `Model '${expertModel}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,\n )\n }\n } catch (modelError) {\n // If we can't get model list, fall back to simple error\n throw new Error(\n `Model '${expertModel}' not found. Please check model configuration or inform user about the issue.`,\n )\n }\n }\n\n // Generic fallback\n throw new Error(\n `Expert model query failed: ${error.message || 'Unknown error'}`,\n )\n }\n\n // Extract answer with error handling\n let expertAnswer: string\n try {\n if (!response?.message?.content) {\n throw new Error('No content in expert response')\n }\n\n expertAnswer = response.message.content\n .filter(block => block.type === 'text')\n .map(block => (block as any).text)\n .join('\\n')\n\n if (!expertAnswer.trim()) {\n throw new Error('Expert response was empty')\n }\n } catch (error) {\n console.error('Failed to extract expert answer:', error)\n logError(error)\n throw new Error('Failed to process expert response')\n }\n\n // Save conversation with error handling\n try {\n addMessageToSession(sessionId, 'user', question)\n addMessageToSession(sessionId, 'assistant', expertAnswer)\n } catch (error) {\n console.error('Failed to save conversation to session:', error)\n logError(error)\n // Don't throw here - we got a valid response, saving is non-critical\n }\n\n const result: Out = {\n chatSessionId: sessionId,\n expertModelName: expertModel,\n expertAnswer: expertAnswer,\n }\n\n yield {\n type: 'result',\n data: result,\n resultForAssistant: this.renderResultForAssistant(result),\n }\n } catch (error: any) {\n // Check if error is due to cancellation\n if (\n error.name === 'AbortError' ||\n abortController.signal?.aborted ||\n isInterrupted\n ) {\n return yield* this.handleInterrupt()\n }\n\n console.error('AskExpertModelTool execution failed:', error)\n logError(error)\n\n // Ensure we have a valid sessionId for error response\n const errorSessionId = sessionId || 'error-session'\n\n const errorMessage =\n error.message || 'Expert consultation failed with unknown error'\n const result: Out = {\n chatSessionId: errorSessionId,\n expertModelName: expertModel,\n expertAnswer: `\u274C ${errorMessage}`,\n }\n\n yield {\n type: 'result',\n data: result,\n resultForAssistant: this.renderResultForAssistant(result),\n }\n } finally {\n // Clean up event listener\n abortController.signal.removeEventListener('abort', abortListener)\n }\n },\n\n // Unified interrupt handling method (following TaskTool pattern)\n async *handleInterrupt() {\n yield {\n type: 'result',\n data: {\n chatSessionId: 'interrupted',\n expertModelName: 'cancelled',\n expertAnswer: INTERRUPT_MESSAGE,\n },\n resultForAssistant: INTERRUPT_MESSAGE,\n }\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { Box, Text } from 'ink'\nimport { z } from 'zod'\nimport { Tool, ValidationResult } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { getModelManager } from '@utils/model'\nimport { getTheme } from '@utils/theme'\nimport {\n createUserMessage,\n createAssistantMessage,\n INTERRUPT_MESSAGE,\n} from '@utils/messages'\nimport { logError } from '@utils/log'\nimport {\n createExpertChatSession,\n loadExpertChatSession,\n getSessionMessages,\n addMessageToSession,\n} from '@utils/expertChatStorage'\nimport { queryLLM } from '@services/claude'\nimport { SEMANTIC_COLORS } from '@constants/colors'\nimport { debug as debugLogger } from '@utils/debugLogger'\nimport { applyMarkdown } from '@utils/markdown'\n\nexport const inputSchema = z.strictObject({\n question: z\n .string()\n .describe(\n 'COMPLETE SELF-CONTAINED QUESTION: Must include full background context, relevant details, and a clear independent question. The expert model will receive ONLY this content with no access to previous conversation or external context. Structure as: 1) Background/Context 2) Specific situation/problem 3) Clear question. Ensure the expert can fully understand and respond without needing additional information.',\n ),\n expert_model: z\n .string()\n .describe(\n 'The expert model to use (e.g., gpt-5, claude-3-5-sonnet-20241022)',\n ),\n chat_session_id: z\n .string()\n .describe(\n 'Chat session ID: use \"new\" for new session or existing session ID',\n ),\n})\n\ntype In = typeof inputSchema\nexport type Out = {\n chatSessionId: string\n expertModelName: string\n expertAnswer: string\n}\n\nexport const AskExpertModelTool = {\n name: 'AskExpertModel',\n async description() {\n return 'Consult external AI models for expert opinions and analysis'\n },\n async prompt() {\n return `Ask a question to a specific external AI model for expert analysis.\n\nThis tool allows you to consult different AI models for their unique perspectives and expertise.\n\nCRITICAL REQUIREMENT FOR QUESTION PARAMETER:\nThe question MUST be completely self-contained and include:\n1. FULL BACKGROUND CONTEXT - All relevant information the expert needs\n2. SPECIFIC SITUATION - Clear description of the current scenario/problem\n3. INDEPENDENT QUESTION - What exactly you want the expert to analyze/answer\n\nThe expert model receives ONLY your question content with NO access to:\n- Previous conversation history (unless using existing session) \n- Current codebase or file context\n- User's current task or project details\n\nIMPORTANT: This tool is for asking questions to models, not for task execution.\n- Use when you need a specific model's opinion or analysis\n- Use when you want to compare different models' responses\n- Use the @ask-[model] format when available\n\nThe expert_model parameter accepts:\n- OpenAI: gpt-4, gpt-5, o1-preview\n- Anthropic: claude-3-5-sonnet, claude-3-opus \n- Others: kimi, gemini-pro, mixtral\n\nExample of well-structured question:\n\"Background: I'm working on a React TypeScript application with performance issues. The app renders a large list of 10,000 items using a simple map() function, causing UI freezing.\n\nCurrent situation: Users report 3-5 second delays when scrolling through the list. The component re-renders the entire list on every state change.\n\nQuestion: What are the most effective React optimization techniques for handling large lists, and how should I prioritize implementing virtualization vs memoization vs other approaches?\"`\n },\n isReadOnly() {\n return true\n },\n isConcurrencySafe() {\n return true\n },\n inputSchema,\n userFacingName() {\n return 'AskExpertModel'\n },\n async isEnabled() {\n return true\n },\n needsPermissions(): boolean {\n return false\n },\n async validateInput(\n { question, expert_model, chat_session_id },\n context?: any,\n ): Promise<ValidationResult> {\n if (!question.trim()) {\n return { result: false, message: 'Question cannot be empty' }\n }\n\n if (!expert_model.trim()) {\n return { result: false, message: 'Expert model must be specified' }\n }\n\n if (!chat_session_id.trim()) {\n return {\n result: false,\n message:\n 'Chat session ID must be specified (use \"new\" for new session)',\n }\n }\n\n // Check if trying to consult the same model we're currently running\n try {\n const modelManager = getModelManager()\n\n // Get current model based on context\n let currentModel: string\n if (context?.agentId && context?.options?.model) {\n // In subagent context (Task tool)\n currentModel = context.options.model\n } else {\n // In main agent context or after model switch\n currentModel = modelManager.getModelName('main') || ''\n }\n\n // Normalize model names for comparison\n const normalizedExpert = expert_model\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n const normalizedCurrent = currentModel\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '')\n\n if (normalizedExpert === normalizedCurrent) {\n return {\n result: false,\n message: `You are already running as ${currentModel}. Consulting the same model would be redundant. Please choose a different model or handle the task directly.`,\n }\n }\n } catch (e) {\n // If we can't determine current model, allow the request\n debugLogger.error('AskExpertModel', {\n message: 'Could not determine current model',\n error: e,\n })\n }\n\n // Validate that the model exists and is available\n try {\n const modelManager = getModelManager()\n const modelResolution = modelManager.resolveModelWithInfo(expert_model)\n\n if (!modelResolution.success) {\n const availableModels = modelManager.getAllAvailableModelNames()\n if (availableModels.length > 0) {\n return {\n result: false,\n message: `Model '${expert_model}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expert_model}' needs to be configured using /model command.`,\n }\n } else {\n return {\n result: false,\n message: `Model '${expert_model}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,\n }\n }\n }\n } catch (error) {\n console.error('Model validation error in AskExpertModelTool:', error)\n logError(error)\n return {\n result: false,\n message: `Failed to validate expert model '${expert_model}'. Please check your model configuration.`,\n }\n }\n\n return { result: true }\n },\n\n renderToolUseMessage(\n { question, expert_model, chat_session_id },\n { verbose },\n ) {\n if (!question || !expert_model) return null\n const isNewSession = chat_session_id === 'new'\n const sessionDisplay = isNewSession\n ? 'new session'\n : `session ${chat_session_id.substring(0, 5)}...`\n const theme = getTheme()\n\n if (verbose) {\n return (\n <Box flexDirection=\"column\">\n <Text bold color=\"yellow\">\n {expert_model}\n </Text>\n <Text color={theme.secondaryText}>{sessionDisplay}</Text>\n <Box marginTop={1}>\n <Text color={theme.text}>\n {question.length > 300\n ? question.substring(0, 300) + '...'\n : question}\n </Text>\n </Box>\n </Box>\n )\n }\n return (\n <Box flexDirection=\"column\">\n <Text bold color=\"yellow\">\n {expert_model}{' '}\n </Text>\n <Text color={SEMANTIC_COLORS.dim}>({sessionDisplay})</Text>\n </Box>\n )\n },\n\n renderToolResultMessage(content) {\n const verbose = true // Show more content\n const theme = getTheme()\n\n // Guard against undefined or null content\n if (!content) {\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation completed</Text>\n </Box>\n )\n }\n\n if (typeof content === 'object' && content && 'expertAnswer' in content) {\n const expertResult = content as Out\n const isError =\n expertResult.expertAnswer.startsWith('Error') ||\n expertResult.expertAnswer.includes('failed')\n const isInterrupted = expertResult.chatSessionId === 'interrupted'\n\n if (isInterrupted) {\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation interrupted</Text>\n </Box>\n )\n }\n\n const answerText = verbose\n ? expertResult.expertAnswer.trim()\n : expertResult.expertAnswer.length > 500\n ? expertResult.expertAnswer.substring(0, 500) + '...'\n : expertResult.expertAnswer.trim()\n\n if (isError) {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\">{answerText}</Text>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text bold color={theme.text}>\n Response from {expertResult.expertModelName}:\n </Text>\n <Box marginTop={1}>\n <Text color={theme.text}>{applyMarkdown(answerText)}</Text>\n </Box>\n <Box marginTop={1}>\n <Text color={SEMANTIC_COLORS.dim}>\n Session: {expertResult.chatSessionId.substring(0, 8)}\n </Text>\n </Box>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"row\">\n <Text color={theme.secondaryText}>Consultation completed</Text>\n </Box>\n )\n },\n\n renderResultForAssistant(output: Out): string {\n return `[Expert consultation completed]\nExpert Model: ${output.expertModelName}\nSession ID: ${output.chatSessionId}\nTo continue this conversation with context preservation, use this Session ID in your next AskExpertModel call to maintain the full conversation history and context.\n\n${output.expertAnswer}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n async *call(\n { question, expert_model, chat_session_id },\n { abortController, readFileTimestamps },\n ) {\n const expertModel = expert_model\n\n let sessionId: string\n let isInterrupted = false\n\n // Set up abort listener (following TaskTool pattern)\n const abortListener = () => {\n isInterrupted = true\n }\n abortController.signal.addEventListener('abort', abortListener)\n\n try {\n // Initial abort check\n if (abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n // Session management with error handling\n if (chat_session_id === 'new') {\n try {\n const session = createExpertChatSession(expertModel)\n sessionId = session.sessionId\n } catch (error) {\n console.error('Failed to create new expert chat session:', error)\n logError(error)\n throw new Error('Failed to create new chat session')\n }\n } else {\n sessionId = chat_session_id\n try {\n const session = loadExpertChatSession(sessionId)\n if (!session) {\n // Session doesn't exist, create new one\n const newSession = createExpertChatSession(expertModel)\n sessionId = newSession.sessionId\n }\n } catch (error) {\n console.error('Failed to load expert chat session:', error)\n logError(error)\n // Fallback: create new session\n try {\n const newSession = createExpertChatSession(expertModel)\n sessionId = newSession.sessionId\n } catch (createError) {\n console.error(\n 'Failed to create fallback expert chat session:',\n createError,\n )\n logError(createError)\n throw new Error('Unable to create or load chat session')\n }\n }\n }\n\n // Check for cancellation before loading history\n if (isInterrupted || abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n\n // Load history and prepare messages with error handling\n let historyMessages: Array<{ role: string; content: string }>\n try {\n historyMessages = getSessionMessages(sessionId)\n } catch (error) {\n console.error('Failed to load session messages:', error)\n logError(error)\n historyMessages = [] // Fallback to empty history\n }\n\n const messages = [...historyMessages, { role: 'user', content: question }]\n\n let systemMessages\n try {\n systemMessages = messages.map(msg =>\n msg.role === 'user'\n ? createUserMessage(msg.content)\n : createAssistantMessage(msg.content),\n )\n } catch (error) {\n console.error('Failed to create system messages:', error)\n logError(error)\n throw new Error('Failed to prepare conversation messages')\n }\n\n // Check for cancellation before model call\n if (isInterrupted || abortController.signal.aborted) {\n return yield* this.handleInterrupt()\n }\n\n // Yield progress message to show we're connecting\n yield {\n type: 'progress',\n content: createAssistantMessage(\n `Connecting to ${expertModel}... (timeout: 5 minutes)`,\n ),\n }\n\n // Call model with comprehensive error handling and timeout\n let response\n try {\n // Debug: Log the model we're trying to use (using global debug logger)\n const modelManager = getModelManager()\n const modelResolution = modelManager.resolveModelWithInfo(expertModel)\n\n debugLogger.api('EXPERT_MODEL_RESOLUTION', {\n requestedModel: expertModel,\n success: modelResolution.success,\n profileName: modelResolution.profile?.name,\n profileModelName: modelResolution.profile?.modelName,\n provider: modelResolution.profile?.provider,\n isActive: modelResolution.profile?.isActive,\n error: modelResolution.error,\n })\n\n // Create a timeout promise to prevent hanging\n const timeoutMs = 300000 // 300 seconds (5 minutes) timeout for external models\n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `Expert model query timed out after ${timeoutMs / 1000}s`,\n ),\n )\n }, timeoutMs)\n })\n\n // Race between the query and timeout\n response = await Promise.race([\n queryLLM(\n systemMessages,\n [], // no system prompt - let expert model use its default behavior\n 0, // no thinking tokens needed\n [], // no tools needed\n abortController.signal,\n {\n safeMode: false,\n model: expertModel,\n prependCLISysprompt: false, // KEY: avoid injecting CLI context\n },\n ),\n timeoutPromise,\n ])\n } catch (error: any) {\n console.error('Expert model query failed:', error)\n logError(error)\n\n // Check for specific error types\n if (\n error.name === 'AbortError' ||\n abortController.signal?.aborted ||\n isInterrupted\n ) {\n return yield* this.handleInterrupt()\n }\n\n if (error.message?.includes('timed out')) {\n throw new Error(\n `Expert model '${expertModel}' timed out after 5 minutes.\\n\\n` +\n `Suggestions:\\n` +\n ` - The model might be experiencing high load\\n` +\n ` - Try a different model or retry later\\n` +\n ` - Consider breaking down your question into smaller parts`,\n )\n }\n\n if (error.message?.includes('rate limit')) {\n throw new Error(\n `Rate limit exceeded for ${expertModel}.\\n\\n` +\n `Please wait a moment and try again, or use a different model.`,\n )\n }\n\n if (error.message?.includes('invalid api key')) {\n throw new Error(\n `Invalid API key for ${expertModel}.\\n\\n` +\n `Please check your model configuration with /model command.`,\n )\n }\n\n if (\n error.message?.includes('model not found') ||\n error.message?.includes('Failed to resolve model')\n ) {\n // Provide helpful model guidance in runtime errors too\n try {\n const modelManager = getModelManager()\n const availableModels = modelManager.getAllAvailableModelNames()\n if (availableModels.length > 0) {\n throw new Error(\n `Model '${expertModel}' is not configured. Available models: ${availableModels.join(', ')}. Check if any available model closely matches the user's request (e.g., 'kimi' matches 'kimi-k2-0711-preview'). If there's a strong match, auto retry using the correct model name. If no close match exists, inform the user that '${expertModel}' needs to be configured using /model command.`,\n )\n } else {\n throw new Error(\n `Model '${expertModel}' not found and no models are currently configured in the system. Inform the user that models need to be configured first using the /model command.`,\n )\n }\n } catch (modelError) {\n // If we can't get model list, fall back to simple error\n throw new Error(\n `Model '${expertModel}' not found. Please check model configuration or inform user about the issue.`,\n )\n }\n }\n\n // Generic fallback\n throw new Error(\n `Expert model query failed: ${error.message || 'Unknown error'}`,\n )\n }\n\n // Extract answer with error handling\n let expertAnswer: string\n try {\n if (!response?.message?.content) {\n throw new Error('No content in expert response')\n }\n\n expertAnswer = response.message.content\n .filter(block => block.type === 'text')\n .map(block => (block as any).text)\n .join('\\n')\n\n if (!expertAnswer.trim()) {\n throw new Error('Expert response was empty')\n }\n } catch (error) {\n console.error('Failed to extract expert answer:', error)\n logError(error)\n throw new Error('Failed to process expert response')\n }\n\n // Save conversation with error handling\n try {\n addMessageToSession(sessionId, 'user', question)\n addMessageToSession(sessionId, 'assistant', expertAnswer)\n } catch (error) {\n console.error('Failed to save conversation to session:', error)\n logError(error)\n // Don't throw here - we got a valid response, saving is non-critical\n }\n\n const result: Out = {\n chatSessionId: sessionId,\n expertModelName: expertModel,\n expertAnswer: expertAnswer,\n }\n\n yield {\n type: 'result',\n data: result,\n resultForAssistant: this.renderResultForAssistant(result),\n }\n } catch (error: any) {\n // Check if error is due to cancellation\n if (\n error.name === 'AbortError' ||\n abortController.signal?.aborted ||\n isInterrupted\n ) {\n return yield* this.handleInterrupt()\n }\n\n console.error('AskExpertModelTool execution failed:', error)\n logError(error)\n\n // Ensure we have a valid sessionId for error response\n const errorSessionId = sessionId || 'error-session'\n\n const errorMessage =\n error.message || 'Expert consultation failed with unknown error'\n const result: Out = {\n chatSessionId: errorSessionId,\n expertModelName: expertModel,\n expertAnswer: `\u274C ${errorMessage}`,\n }\n\n yield {\n type: 'result',\n data: result,\n resultForAssistant: this.renderResultForAssistant(result),\n }\n } finally {\n // Clean up event listener\n abortController.signal.removeEventListener('abort', abortListener)\n }\n },\n\n // Unified interrupt handling method (following TaskTool pattern)\n async *handleInterrupt() {\n yield {\n type: 'result',\n data: {\n chatSessionId: 'interrupted',\n expertModelName: 'cancelled',\n expertAnswer: INTERRUPT_MESSAGE,\n },\n resultForAssistant: INTERRUPT_MESSAGE,\n }\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import * as React from "react";
3
3
  import { z } from "zod";
4
4
  import { FallbackToolUseRejectedMessage } from "../../components/FallbackToolUseRejectedMessage.js";
5
5
  import { DESCRIPTION, PROMPT } from "./prompt.js";
6
+ import { SEMANTIC_COLORS } from "../../constants/colors.js";
6
7
  const inputSchema = z.strictObject({
7
8
  questions: z.array(
8
9
  z.object({
@@ -70,7 +71,7 @@ ${formattedAnswers}`;
70
71
  const answers = output.answers || [];
71
72
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingLeft: 2 }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, { color: "#10B981" }, " ", "\u23BF "), /* @__PURE__ */ React.createElement(Text, null, "User provided answers")), answers.map((answer, index) => {
72
73
  if (!answer) return null;
73
- return /* @__PURE__ */ React.createElement(Box, { key: index, flexDirection: "row", paddingLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#6B7280" }, "\u2022 "), answer.customInput ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ': "', answer.customInput, '"') : answer.selectedOptions ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ": Option", " ", answer.selectedOptions.map((i) => i + 1).join(", ")) : /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Q", (answer.questionIndex ?? index) + 1, ": No answer"));
74
+ return /* @__PURE__ */ React.createElement(Box, { key: index, flexDirection: "row", paddingLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#6B7280" }, "\u2022 "), answer.customInput ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ': "', answer.customInput, '"') : answer.selectedOptions ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ": Option", " ", answer.selectedOptions.map((i) => i + 1).join(", ")) : /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Q", (answer.questionIndex ?? index) + 1, ": No answer"));
74
75
  }));
75
76
  },
76
77
  async *call({ questions }, context) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx"],
4
- "sourcesContent": ["import { Box, Text } from 'ink'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Tool, type ExtendedToolUseContext } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport type { UserAnswer } from '@minto-types/askUserQuestion'\n\nconst inputSchema = z.strictObject({\n questions: z\n .array(\n z.object({\n question: z.string().describe('The question to ask the user'),\n header: z\n .string()\n .max(12)\n .optional()\n .describe('Short category label (max 12 chars)'),\n options: z\n .array(\n z.object({\n label: z.string().describe('Option display text'),\n description: z.string().optional().describe('Option explanation'),\n }),\n )\n .min(2)\n .max(4)\n .optional()\n .describe('List of options (2-4 recommended)'),\n multiSelect: z\n .boolean()\n .optional()\n .default(false)\n .describe('Allow multiple selections'),\n }),\n )\n .min(1)\n .max(4)\n .describe('Questions to ask (1-4 questions)'),\n})\n\nexport type AskUserQuestionOutput = {\n answers: UserAnswer[]\n timestamp: number\n}\n\nexport const AskUserQuestionTool = {\n name: 'AskUserQuestion',\n\n async description() {\n return DESCRIPTION\n },\n\n async prompt() {\n return PROMPT\n },\n\n inputSchema,\n\n userFacingName() {\n return 'Ask User Question'\n },\n\n async isEnabled() {\n return true\n },\n\n isReadOnly() {\n return true // Doesn't modify files\n },\n\n isConcurrencySafe() {\n return false // User interaction is not concurrent-safe\n },\n\n needsPermissions() {\n return false // No special permissions needed\n },\n\n renderResultForAssistant(output: AskUserQuestionOutput) {\n const formattedAnswers = output.answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')\n\n return `User answered:\\n${formattedAnswers}`\n },\n\n renderToolUseMessage(input: z.infer<typeof inputSchema>) {\n const questionCount = input.questions.length\n const questionText =\n questionCount === 1\n ? input.questions[0]!.question\n : `${questionCount} questions`\n return `Asking user: ${questionText}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n renderToolResultMessage(output: AskUserQuestionOutput) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>Question completed</Text>\n </Box>\n </Box>\n )\n }\n\n const answers = output.answers || []\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>User provided answers</Text>\n </Box>\n {answers.map((answer, index) => {\n // Guard against null/undefined answers\n if (!answer) return null\n return (\n <Box key={index} flexDirection=\"row\" paddingLeft={4}>\n <Text color=\"#6B7280\">\u2022 </Text>\n {answer.customInput ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: \"{answer.customInput}\"\n </Text>\n ) : answer.selectedOptions ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: Option{' '}\n {answer.selectedOptions.map(i => i + 1).join(', ')}\n </Text>\n ) : (\n <Text dimColor>\n Q{(answer.questionIndex ?? index) + 1}: No answer\n </Text>\n )}\n </Box>\n )\n })}\n </Box>\n )\n },\n\n async *call(\n { questions }: z.infer<typeof inputSchema>,\n context: ExtendedToolUseContext,\n ) {\n // Check if askUser function is available\n if (!context.askUser) {\n throw new Error(\n 'AskUserQuestion tool requires askUser function in context',\n )\n }\n\n try {\n // Convert parsed questions to UserQuestion type\n const userQuestions = questions.map(q => ({\n question: q.question,\n header: q.header,\n options: q.options?.map(opt => ({\n label: opt.label,\n description: opt.description,\n })),\n multiSelect: q.multiSelect ?? false,\n }))\n\n // Call askUser and wait for user response\n const answers = await context.askUser(userQuestions)\n\n // Return the result\n yield {\n type: 'result',\n data: {\n answers,\n timestamp: Date.now(),\n },\n resultForAssistant: `User answered:\\n${answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')}`,\n }\n } catch (error) {\n // User cancelled or error occurred\n throw new Error(\n `Failed to get user response: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n },\n} satisfies Tool<typeof inputSchema, AskUserQuestionOutput>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Box, Text } from 'ink'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Tool, type ExtendedToolUseContext } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport type { UserAnswer } from '@minto-types/askUserQuestion'\nimport { SEMANTIC_COLORS } from '@constants/colors'\n\nconst inputSchema = z.strictObject({\n questions: z\n .array(\n z.object({\n question: z.string().describe('The question to ask the user'),\n header: z\n .string()\n .max(12)\n .optional()\n .describe('Short category label (max 12 chars)'),\n options: z\n .array(\n z.object({\n label: z.string().describe('Option display text'),\n description: z.string().optional().describe('Option explanation'),\n }),\n )\n .min(2)\n .max(4)\n .optional()\n .describe('List of options (2-4 recommended)'),\n multiSelect: z\n .boolean()\n .optional()\n .default(false)\n .describe('Allow multiple selections'),\n }),\n )\n .min(1)\n .max(4)\n .describe('Questions to ask (1-4 questions)'),\n})\n\nexport type AskUserQuestionOutput = {\n answers: UserAnswer[]\n timestamp: number\n}\n\nexport const AskUserQuestionTool = {\n name: 'AskUserQuestion',\n\n async description() {\n return DESCRIPTION\n },\n\n async prompt() {\n return PROMPT\n },\n\n inputSchema,\n\n userFacingName() {\n return 'Ask User Question'\n },\n\n async isEnabled() {\n return true\n },\n\n isReadOnly() {\n return true // Doesn't modify files\n },\n\n isConcurrencySafe() {\n return false // User interaction is not concurrent-safe\n },\n\n needsPermissions() {\n return false // No special permissions needed\n },\n\n renderResultForAssistant(output: AskUserQuestionOutput) {\n const formattedAnswers = output.answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')\n\n return `User answered:\\n${formattedAnswers}`\n },\n\n renderToolUseMessage(input: z.infer<typeof inputSchema>) {\n const questionCount = input.questions.length\n const questionText =\n questionCount === 1\n ? input.questions[0]!.question\n : `${questionCount} questions`\n return `Asking user: ${questionText}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n renderToolResultMessage(output: AskUserQuestionOutput) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>Question completed</Text>\n </Box>\n </Box>\n )\n }\n\n const answers = output.answers || []\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>User provided answers</Text>\n </Box>\n {answers.map((answer, index) => {\n // Guard against null/undefined answers\n if (!answer) return null\n return (\n <Box key={index} flexDirection=\"row\" paddingLeft={4}>\n <Text color=\"#6B7280\">\u2022 </Text>\n {answer.customInput ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: \"{answer.customInput}\"\n </Text>\n ) : answer.selectedOptions ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: Option{' '}\n {answer.selectedOptions.map(i => i + 1).join(', ')}\n </Text>\n ) : (\n <Text color={SEMANTIC_COLORS.dim}>\n Q{(answer.questionIndex ?? index) + 1}: No answer\n </Text>\n )}\n </Box>\n )\n })}\n </Box>\n )\n },\n\n async *call(\n { questions }: z.infer<typeof inputSchema>,\n context: ExtendedToolUseContext,\n ) {\n // Check if askUser function is available\n if (!context.askUser) {\n throw new Error(\n 'AskUserQuestion tool requires askUser function in context',\n )\n }\n\n try {\n // Convert parsed questions to UserQuestion type\n const userQuestions = questions.map(q => ({\n question: q.question,\n header: q.header,\n options: q.options?.map(opt => ({\n label: opt.label,\n description: opt.description,\n })),\n multiSelect: q.multiSelect ?? false,\n }))\n\n // Call askUser and wait for user response\n const answers = await context.askUser(userQuestions)\n\n // Return the result\n yield {\n type: 'result',\n data: {\n answers,\n timestamp: Date.now(),\n },\n resultForAssistant: `User answered:\\n${answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')}`,\n }\n } catch (error) {\n // User cancelled or error occurred\n throw new Error(\n `Failed to get user response: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n },\n} satisfies Tool<typeof inputSchema, AskUserQuestionOutput>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,7 +16,11 @@ import { getGlobalConfig } from "../../utils/config.js";
16
16
  import { getModelManager } from "../../utils/model.js";
17
17
  import { BackgroundShellManager } from "../../utils/BackgroundShellManager.js";
18
18
  import BashToolResultMessage from "./BashToolResultMessage.js";
19
- import { BANNED_COMMANDS, PROMPT, matchesDangerousPattern } from "./prompt.js";
19
+ import {
20
+ BANNED_COMMANDS,
21
+ PROMPT,
22
+ detectDangerousPatterns
23
+ } from "./prompt.js";
20
24
  import { formatOutput, getCommandFilePaths } from "./utils.js";
21
25
  const inputSchema = z.strictObject({
22
26
  command: z.string().describe("The command to execute"),
@@ -51,12 +55,27 @@ const BashTool = {
51
55
  return true;
52
56
  },
53
57
  async validateInput({ command }) {
54
- if (matchesDangerousPattern(command)) {
58
+ const dangerousPatterns = detectDangerousPatterns(command);
59
+ const criticalPatterns = dangerousPatterns.filter(
60
+ (p) => p.severity === "critical"
61
+ );
62
+ if (criticalPatterns.length > 0) {
63
+ const patternNames = criticalPatterns.map((p) => p.name).join(", ");
64
+ logError(
65
+ `SECURITY: Critical dangerous patterns detected in command: ${patternNames}`
66
+ );
55
67
  return {
56
68
  result: false,
57
- message: `Command contains dangerous patterns and has been blocked for security reasons`
69
+ message: `Command contains critical security risks: ${patternNames}. This command has been blocked for security reasons.`
58
70
  };
59
71
  }
72
+ const highPatterns = dangerousPatterns.filter((p) => p.severity === "high");
73
+ if (highPatterns.length > 0) {
74
+ const patternNames = highPatterns.map((p) => p.name).join(", ");
75
+ logError(
76
+ `SECURITY WARNING: High-severity patterns detected in command: ${patternNames}. User confirmation required.`
77
+ );
78
+ }
60
79
  const commands = splitCommand(command);
61
80
  for (const cmd of commands) {
62
81
  const parts = cmd.split(" ");