@within-7/minto 0.2.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/dist/commands/agents/AgentsCommand.js +22 -24
  2. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  3. package/dist/commands/context.js +2 -1
  4. package/dist/commands/context.js.map +2 -2
  5. package/dist/commands/export.js +2 -1
  6. package/dist/commands/export.js.map +2 -2
  7. package/dist/commands/mcp-interactive.js +7 -6
  8. package/dist/commands/mcp-interactive.js.map +2 -2
  9. package/dist/commands/model.js +3 -2
  10. package/dist/commands/model.js.map +2 -2
  11. package/dist/commands/permissions.js +4 -3
  12. package/dist/commands/permissions.js.map +2 -2
  13. package/dist/commands/plugin/AddMarketplaceForm.js +3 -2
  14. package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
  15. package/dist/commands/plugin/ConfirmDialog.js +2 -1
  16. package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
  17. package/dist/commands/plugin/ErrorView.js +2 -1
  18. package/dist/commands/plugin/ErrorView.js.map +2 -2
  19. package/dist/commands/plugin/InstalledPluginsByMarketplace.js +5 -4
  20. package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
  21. package/dist/commands/plugin/InstalledPluginsManager.js +5 -4
  22. package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
  23. package/dist/commands/plugin/MainMenu.js +2 -1
  24. package/dist/commands/plugin/MainMenu.js.map +2 -2
  25. package/dist/commands/plugin/MarketplaceManager.js +5 -4
  26. package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
  27. package/dist/commands/plugin/MarketplaceSelector.js +4 -3
  28. package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
  29. package/dist/commands/plugin/PlaceholderScreen.js +3 -2
  30. package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
  31. package/dist/commands/plugin/PluginBrowser.js +6 -5
  32. package/dist/commands/plugin/PluginBrowser.js.map +2 -2
  33. package/dist/commands/plugin/PluginDetailsInstall.js +5 -4
  34. package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
  35. package/dist/commands/plugin/PluginDetailsManage.js +4 -3
  36. package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
  37. package/dist/commands/plugin.js +16 -15
  38. package/dist/commands/plugin.js.map +2 -2
  39. package/dist/commands/sandbox.js +4 -3
  40. package/dist/commands/sandbox.js.map +2 -2
  41. package/dist/commands/setup.js +2 -1
  42. package/dist/commands/setup.js.map +2 -2
  43. package/dist/commands/status.js +2 -1
  44. package/dist/commands/status.js.map +2 -2
  45. package/dist/commands/undo.js +245 -0
  46. package/dist/commands/undo.js.map +7 -0
  47. package/dist/commands.js +2 -0
  48. package/dist/commands.js.map +2 -2
  49. package/dist/components/AgentThinkingBlock.js +1 -1
  50. package/dist/components/AgentThinkingBlock.js.map +2 -2
  51. package/dist/components/AsciiLogo.js +7 -8
  52. package/dist/components/AsciiLogo.js.map +2 -2
  53. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  54. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  55. package/dist/components/AskUserQuestionDialog/QuestionView.js +2 -1
  56. package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
  57. package/dist/components/CollapsibleHint.js +2 -1
  58. package/dist/components/CollapsibleHint.js.map +2 -2
  59. package/dist/components/Config.js +3 -2
  60. package/dist/components/Config.js.map +2 -2
  61. package/dist/components/ConsoleOAuthFlow.js +2 -1
  62. package/dist/components/ConsoleOAuthFlow.js.map +2 -2
  63. package/dist/components/Cost.js +2 -1
  64. package/dist/components/Cost.js.map +2 -2
  65. package/dist/components/HeaderBar.js +13 -8
  66. package/dist/components/HeaderBar.js.map +2 -2
  67. package/dist/components/HistorySearchOverlay.js +4 -3
  68. package/dist/components/HistorySearchOverlay.js.map +2 -2
  69. package/dist/components/HotkeyHelpPanel.js +8 -11
  70. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  71. package/dist/components/InvalidConfigDialog.js +2 -1
  72. package/dist/components/InvalidConfigDialog.js.map +2 -2
  73. package/dist/components/Logo.js +23 -67
  74. package/dist/components/Logo.js.map +2 -2
  75. package/dist/components/MCPServerApprovalDialog.js +2 -1
  76. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  77. package/dist/components/MCPServerDialogCopy.js +2 -1
  78. package/dist/components/MCPServerDialogCopy.js.map +2 -2
  79. package/dist/components/MCPServerMultiselectDialog.js +2 -1
  80. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  81. package/dist/components/MessageSelector.js +4 -3
  82. package/dist/components/MessageSelector.js.map +2 -2
  83. package/dist/components/ModeIndicator.js +2 -1
  84. package/dist/components/ModeIndicator.js.map +2 -2
  85. package/dist/components/ModelConfig.js +4 -3
  86. package/dist/components/ModelConfig.js.map +2 -2
  87. package/dist/components/ModelListManager.js +4 -3
  88. package/dist/components/ModelListManager.js.map +2 -2
  89. package/dist/components/ModelSelector/ModelSelector.js +26 -13
  90. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  91. package/dist/components/Onboarding.js +3 -2
  92. package/dist/components/Onboarding.js.map +2 -2
  93. package/dist/components/OperationSummary.js +130 -0
  94. package/dist/components/OperationSummary.js.map +7 -0
  95. package/dist/components/PromptInput.js +88 -75
  96. package/dist/components/PromptInput.js.map +2 -2
  97. package/dist/components/SensitiveFileWarning.js +31 -0
  98. package/dist/components/SensitiveFileWarning.js.map +7 -0
  99. package/dist/components/Spinner.js +71 -22
  100. package/dist/components/Spinner.js.map +2 -2
  101. package/dist/components/StructuredDiff.js +6 -8
  102. package/dist/components/StructuredDiff.js.map +2 -2
  103. package/dist/components/SubagentBlock.js +4 -2
  104. package/dist/components/SubagentBlock.js.map +2 -2
  105. package/dist/components/SubagentProgress.js +17 -6
  106. package/dist/components/SubagentProgress.js.map +2 -2
  107. package/dist/components/TaskCard.js +14 -11
  108. package/dist/components/TaskCard.js.map +2 -2
  109. package/dist/components/TextInput.js +9 -1
  110. package/dist/components/TextInput.js.map +2 -2
  111. package/dist/components/TodoPanel.js +44 -26
  112. package/dist/components/TodoPanel.js.map +2 -2
  113. package/dist/components/ToolUseLoader.js +2 -2
  114. package/dist/components/ToolUseLoader.js.map +2 -2
  115. package/dist/components/TreeConnector.js +4 -3
  116. package/dist/components/TreeConnector.js.map +2 -2
  117. package/dist/components/TrustDialog.js +2 -1
  118. package/dist/components/TrustDialog.js.map +2 -2
  119. package/dist/components/binary-feedback/BinaryFeedbackView.js +2 -1
  120. package/dist/components/binary-feedback/BinaryFeedbackView.js.map +2 -2
  121. package/dist/components/messages/AssistantTextMessage.js +17 -9
  122. package/dist/components/messages/AssistantTextMessage.js.map +2 -2
  123. package/dist/components/messages/AssistantToolUseMessage.js +8 -4
  124. package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
  125. package/dist/components/messages/GroupRenderer.js +2 -1
  126. package/dist/components/messages/GroupRenderer.js.map +2 -2
  127. package/dist/components/messages/NestedTasksPreview.js +13 -1
  128. package/dist/components/messages/NestedTasksPreview.js.map +2 -2
  129. package/dist/components/messages/ParallelTasksGroupView.js +4 -3
  130. package/dist/components/messages/ParallelTasksGroupView.js.map +2 -2
  131. package/dist/components/messages/TaskInModuleView.js +35 -15
  132. package/dist/components/messages/TaskInModuleView.js.map +2 -2
  133. package/dist/components/messages/TaskOutputContent.js +9 -6
  134. package/dist/components/messages/TaskOutputContent.js.map +2 -2
  135. package/dist/components/messages/UserPromptMessage.js +2 -2
  136. package/dist/components/messages/UserPromptMessage.js.map +2 -2
  137. package/dist/constants/colors.js +90 -72
  138. package/dist/constants/colors.js.map +2 -2
  139. package/dist/constants/prompts.js +22 -1
  140. package/dist/constants/prompts.js.map +2 -2
  141. package/dist/constants/toolInputExamples.js +84 -0
  142. package/dist/constants/toolInputExamples.js.map +7 -0
  143. package/dist/core/backupManager.js +321 -0
  144. package/dist/core/backupManager.js.map +7 -0
  145. package/dist/core/costTracker.js +9 -18
  146. package/dist/core/costTracker.js.map +2 -2
  147. package/dist/core/gitAutoCommit.js +287 -0
  148. package/dist/core/gitAutoCommit.js.map +7 -0
  149. package/dist/core/index.js +3 -0
  150. package/dist/core/index.js.map +2 -2
  151. package/dist/core/operationTracker.js +212 -0
  152. package/dist/core/operationTracker.js.map +7 -0
  153. package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
  154. package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
  155. package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
  156. package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
  157. package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
  158. package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
  159. package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
  160. package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
  161. package/dist/core/tokenStats.js +9 -0
  162. package/dist/core/tokenStats.js.map +7 -0
  163. package/dist/core/tokenStatsManager.js +331 -0
  164. package/dist/core/tokenStatsManager.js.map +7 -0
  165. package/dist/entrypoints/cli.js +122 -88
  166. package/dist/entrypoints/cli.js.map +2 -2
  167. package/dist/hooks/useAgentTokenStats.js +72 -0
  168. package/dist/hooks/useAgentTokenStats.js.map +7 -0
  169. package/dist/hooks/useAgentTranscripts.js +30 -6
  170. package/dist/hooks/useAgentTranscripts.js.map +2 -2
  171. package/dist/hooks/useLogMessages.js +12 -1
  172. package/dist/hooks/useLogMessages.js.map +2 -2
  173. package/dist/i18n/locales/en.js +6 -5
  174. package/dist/i18n/locales/en.js.map +2 -2
  175. package/dist/i18n/locales/zh-CN.js +6 -5
  176. package/dist/i18n/locales/zh-CN.js.map +2 -2
  177. package/dist/i18n/types.js.map +1 -1
  178. package/dist/permissions.js +147 -1
  179. package/dist/permissions.js.map +2 -2
  180. package/dist/query.js +78 -4
  181. package/dist/query.js.map +3 -3
  182. package/dist/screens/REPL.js +23 -3
  183. package/dist/screens/REPL.js.map +2 -2
  184. package/dist/screens/ResumeConversation.js +2 -0
  185. package/dist/screens/ResumeConversation.js.map +2 -2
  186. package/dist/services/claude.js +54 -3
  187. package/dist/services/claude.js.map +2 -2
  188. package/dist/services/intelligentCompactor.js +1 -1
  189. package/dist/services/intelligentCompactor.js.map +2 -2
  190. package/dist/services/mcpClient.js +81 -25
  191. package/dist/services/mcpClient.js.map +2 -2
  192. package/dist/services/sandbox/filesystemBoundary.js +58 -17
  193. package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
  194. package/dist/services/taskStore.js +205 -0
  195. package/dist/services/taskStore.js.map +7 -0
  196. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
  197. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
  198. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +42 -4
  199. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  200. package/dist/tools/BashTool/BashTool.js +43 -7
  201. package/dist/tools/BashTool/BashTool.js.map +2 -2
  202. package/dist/tools/BashTool/prompt.js +184 -34
  203. package/dist/tools/BashTool/prompt.js.map +2 -2
  204. package/dist/tools/FileEditTool/FileEditTool.js +24 -9
  205. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  206. package/dist/tools/FileEditTool/prompt.js +10 -4
  207. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  208. package/dist/tools/FileEditTool/utils.js +10 -4
  209. package/dist/tools/FileEditTool/utils.js.map +2 -2
  210. package/dist/tools/FileReadTool/FileReadTool.js +1 -1
  211. package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
  212. package/dist/tools/FileReadTool/prompt.js +16 -1
  213. package/dist/tools/FileReadTool/prompt.js.map +2 -2
  214. package/dist/tools/FileWriteTool/FileWriteTool.js +1 -1
  215. package/dist/tools/FileWriteTool/FileWriteTool.js.map +1 -1
  216. package/dist/tools/FileWriteTool/prompt.js +12 -3
  217. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  218. package/dist/tools/GlobTool/prompt.js +12 -1
  219. package/dist/tools/GlobTool/prompt.js.map +2 -2
  220. package/dist/tools/GrepTool/GrepTool.js +333 -65
  221. package/dist/tools/GrepTool/GrepTool.js.map +2 -2
  222. package/dist/tools/GrepTool/prompt.js +15 -8
  223. package/dist/tools/GrepTool/prompt.js.map +2 -2
  224. package/dist/tools/MultiEditTool/prompt.js +5 -3
  225. package/dist/tools/MultiEditTool/prompt.js.map +2 -2
  226. package/dist/tools/NotebookEditTool/NotebookEditTool.js +59 -46
  227. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  228. package/dist/tools/NotebookEditTool/prompt.js +1 -1
  229. package/dist/tools/NotebookEditTool/prompt.js.map +1 -1
  230. package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
  231. package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
  232. package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
  233. package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
  234. package/dist/tools/PlanModeTool/prompt.js +1 -1
  235. package/dist/tools/PlanModeTool/prompt.js.map +1 -1
  236. package/dist/tools/SkillTool/SkillTool.js +4 -3
  237. package/dist/tools/SkillTool/SkillTool.js.map +2 -2
  238. package/dist/tools/SkillTool/prompt.js +1 -1
  239. package/dist/tools/SkillTool/prompt.js.map +1 -1
  240. package/dist/tools/TaskCreateTool/TaskCreateTool.js +102 -0
  241. package/dist/tools/TaskCreateTool/TaskCreateTool.js.map +7 -0
  242. package/dist/tools/TaskCreateTool/prompt.js +47 -0
  243. package/dist/tools/TaskCreateTool/prompt.js.map +7 -0
  244. package/dist/tools/TaskGetTool/TaskGetTool.js +115 -0
  245. package/dist/tools/TaskGetTool/TaskGetTool.js.map +7 -0
  246. package/dist/tools/TaskGetTool/prompt.js +28 -0
  247. package/dist/tools/TaskGetTool/prompt.js.map +7 -0
  248. package/dist/tools/TaskListTool/TaskListTool.js +102 -0
  249. package/dist/tools/TaskListTool/TaskListTool.js.map +7 -0
  250. package/dist/tools/TaskListTool/prompt.js +27 -0
  251. package/dist/tools/TaskListTool/prompt.js.map +7 -0
  252. package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
  253. package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
  254. package/dist/tools/TaskStopTool/TaskStopTool.js +150 -0
  255. package/dist/tools/TaskStopTool/TaskStopTool.js.map +7 -0
  256. package/dist/tools/TaskStopTool/prompt.js +15 -0
  257. package/dist/tools/TaskStopTool/prompt.js.map +7 -0
  258. package/dist/tools/TaskTool/TaskTool.js +49 -1
  259. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  260. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js +134 -0
  261. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js.map +7 -0
  262. package/dist/tools/TaskUpdateTool/prompt.js +81 -0
  263. package/dist/tools/TaskUpdateTool/prompt.js.map +7 -0
  264. package/dist/tools/URLFetcherTool/prompt.js +1 -1
  265. package/dist/tools/URLFetcherTool/prompt.js.map +1 -1
  266. package/dist/tools.js +12 -0
  267. package/dist/tools.js.map +2 -2
  268. package/dist/utils/CircuitBreaker.js +242 -0
  269. package/dist/utils/CircuitBreaker.js.map +7 -0
  270. package/dist/utils/ask.js +2 -0
  271. package/dist/utils/ask.js.map +2 -2
  272. package/dist/utils/config.js +47 -5
  273. package/dist/utils/config.js.map +2 -2
  274. package/dist/utils/credentials/CredentialStore.js +1 -0
  275. package/dist/utils/credentials/CredentialStore.js.map +7 -0
  276. package/dist/utils/credentials/EncryptedFileStore.js +157 -0
  277. package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
  278. package/dist/utils/credentials/index.js +37 -0
  279. package/dist/utils/credentials/index.js.map +7 -0
  280. package/dist/utils/credentials/migration.js +82 -0
  281. package/dist/utils/credentials/migration.js.map +7 -0
  282. package/dist/utils/markdown.js +13 -1
  283. package/dist/utils/markdown.js.map +2 -2
  284. package/dist/utils/model.js +15 -2
  285. package/dist/utils/model.js.map +2 -2
  286. package/dist/utils/permissions/filesystem.js +5 -1
  287. package/dist/utils/permissions/filesystem.js.map +2 -2
  288. package/dist/utils/ripgrep.js +53 -1
  289. package/dist/utils/ripgrep.js.map +2 -2
  290. package/dist/utils/safePath.js +132 -0
  291. package/dist/utils/safePath.js.map +7 -0
  292. package/dist/utils/sensitiveFiles.js +125 -0
  293. package/dist/utils/sensitiveFiles.js.map +7 -0
  294. package/dist/utils/taskDisplayUtils.js +9 -9
  295. package/dist/utils/taskDisplayUtils.js.map +2 -2
  296. package/dist/utils/terminal.js +12 -0
  297. package/dist/utils/terminal.js.map +2 -2
  298. package/dist/utils/theme.js +6 -6
  299. package/dist/utils/theme.js.map +1 -1
  300. package/dist/utils/toolRiskClassification.js +207 -0
  301. package/dist/utils/toolRiskClassification.js.map +7 -0
  302. package/dist/utils/tooling/safeRender.js +17 -17
  303. package/dist/utils/tooling/safeRender.js.map +2 -2
  304. package/dist/version.js +2 -2
  305. package/dist/version.js.map +1 -1
  306. package/package.json +22 -28
  307. package/dist/hooks/useCancelRequest.js +0 -31
  308. package/dist/hooks/useCancelRequest.js.map +0 -7
@@ -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
  }
@@ -0,0 +1,205 @@
1
+ import { getSessionState, setSessionState } from "../utils/sessionState.js";
2
+ import { emitReminderEvent } from "./systemReminder.js";
3
+ const TASK_STORAGE_KEY = "claudeCodeTasks";
4
+ let taskCache = null;
5
+ let cacheTimestamp = 0;
6
+ const CACHE_TTL = 5e3;
7
+ let nextTaskId = 1;
8
+ function invalidateCache() {
9
+ taskCache = null;
10
+ cacheTimestamp = 0;
11
+ }
12
+ function getNextTaskId() {
13
+ const tasks = getAllTasks();
14
+ const existingIds = tasks.map((t) => parseInt(t.id, 10)).filter((id) => !isNaN(id));
15
+ if (existingIds.length > 0) {
16
+ nextTaskId = Math.max(...existingIds) + 1;
17
+ }
18
+ return String(nextTaskId++);
19
+ }
20
+ function generateActiveForm(subject) {
21
+ const trimmed = subject.trim();
22
+ const patterns = [
23
+ { regex: /^(Run|run)\s+(.+)$/i, replacement: "Running $2" },
24
+ { regex: /^(Build|build)\s+(.+)$/i, replacement: "Building $2" },
25
+ { regex: /^(Fix|fix)\s+(.+)$/i, replacement: "Fixing $2" },
26
+ { regex: /^(Add|add)\s+(.+)$/i, replacement: "Adding $2" },
27
+ { regex: /^(Create|create)\s+(.+)$/i, replacement: "Creating $2" },
28
+ { regex: /^(Update|update)\s+(.+)$/i, replacement: "Updating $2" },
29
+ { regex: /^(Delete|delete)\s+(.+)$/i, replacement: "Deleting $2" },
30
+ { regex: /^(Test|test)\s+(.+)$/i, replacement: "Testing $2" },
31
+ { regex: /^(Deploy|deploy)\s+(.+)$/i, replacement: "Deploying $2" },
32
+ { regex: /^(Analyze|analyze)\s+(.+)$/i, replacement: "Analyzing $2" },
33
+ { regex: /^(Review|review)\s+(.+)$/i, replacement: "Reviewing $2" },
34
+ { regex: /^(Write|write)\s+(.+)$/i, replacement: "Writing $2" },
35
+ {
36
+ regex: /^(Implement|implement)\s+(.+)$/i,
37
+ replacement: "Implementing $2"
38
+ },
39
+ { regex: /^(Refactor|refactor)\s+(.+)$/i, replacement: "Refactoring $2" },
40
+ { regex: /^(Debug|debug)\s+(.+)$/i, replacement: "Debugging $2" },
41
+ { regex: /^(Configure|configure)\s+(.+)$/i, replacement: "Configuring $2" },
42
+ { regex: /^(Install|install)\s+(.+)$/i, replacement: "Installing $2" },
43
+ { regex: /^(Remove|remove)\s+(.+)$/i, replacement: "Removing $2" },
44
+ { regex: /^(Verify|verify)\s+(.+)$/i, replacement: "Verifying $2" },
45
+ { regex: /^(Check|check)\s+(.+)$/i, replacement: "Checking $2" }
46
+ ];
47
+ for (const { regex, replacement } of patterns) {
48
+ if (regex.test(trimmed)) {
49
+ return trimmed.replace(regex, replacement);
50
+ }
51
+ }
52
+ return `Working on: ${trimmed}`;
53
+ }
54
+ function getAllTasks() {
55
+ const now = Date.now();
56
+ if (taskCache && now - cacheTimestamp < CACHE_TTL) {
57
+ return taskCache;
58
+ }
59
+ const sessionState = getSessionState();
60
+ const tasks = sessionState[TASK_STORAGE_KEY] || [];
61
+ taskCache = [...tasks];
62
+ cacheTimestamp = now;
63
+ return tasks;
64
+ }
65
+ function saveTasks(tasks) {
66
+ setSessionState({
67
+ ...getSessionState(),
68
+ [TASK_STORAGE_KEY]: tasks
69
+ });
70
+ invalidateCache();
71
+ }
72
+ function createTask(input) {
73
+ const tasks = getAllTasks();
74
+ const now = Date.now();
75
+ const newTask = {
76
+ id: getNextTaskId(),
77
+ subject: input.subject,
78
+ description: input.description,
79
+ status: "pending",
80
+ activeForm: input.activeForm || generateActiveForm(input.subject),
81
+ metadata: input.metadata,
82
+ createdAt: now,
83
+ updatedAt: now
84
+ };
85
+ const updatedTasks = [...tasks, newTask];
86
+ saveTasks(updatedTasks);
87
+ emitReminderEvent("task:created", {
88
+ task: newTask,
89
+ timestamp: now
90
+ });
91
+ return newTask;
92
+ }
93
+ function updateTask(input) {
94
+ const tasks = getAllTasks();
95
+ const taskIndex = tasks.findIndex((t) => t.id === input.taskId);
96
+ if (taskIndex === -1) {
97
+ return null;
98
+ }
99
+ const existingTask = tasks[taskIndex];
100
+ const now = Date.now();
101
+ if (input.status === "deleted") {
102
+ const updatedTasks2 = tasks.filter((t) => t.id !== input.taskId);
103
+ saveTasks(updatedTasks2);
104
+ emitReminderEvent("task:deleted", {
105
+ taskId: input.taskId,
106
+ timestamp: now
107
+ });
108
+ return existingTask;
109
+ }
110
+ const updatedTask = {
111
+ ...existingTask,
112
+ updatedAt: now
113
+ };
114
+ if (input.status !== void 0) {
115
+ updatedTask.status = input.status;
116
+ }
117
+ if (input.subject !== void 0) {
118
+ updatedTask.subject = input.subject;
119
+ }
120
+ if (input.description !== void 0) {
121
+ updatedTask.description = input.description;
122
+ }
123
+ if (input.activeForm !== void 0) {
124
+ updatedTask.activeForm = input.activeForm;
125
+ }
126
+ if (input.owner !== void 0) {
127
+ updatedTask.owner = input.owner;
128
+ }
129
+ if (input.metadata !== void 0) {
130
+ const newMetadata = { ...existingTask.metadata };
131
+ for (const [key, value] of Object.entries(input.metadata)) {
132
+ if (value === null) {
133
+ delete newMetadata[key];
134
+ } else {
135
+ newMetadata[key] = value;
136
+ }
137
+ }
138
+ updatedTask.metadata = Object.keys(newMetadata).length > 0 ? newMetadata : void 0;
139
+ }
140
+ if (input.addBlocks && input.addBlocks.length > 0) {
141
+ const currentBlocks = new Set(existingTask.blocks || []);
142
+ input.addBlocks.forEach((id) => currentBlocks.add(id));
143
+ updatedTask.blocks = Array.from(currentBlocks);
144
+ }
145
+ if (input.addBlockedBy && input.addBlockedBy.length > 0) {
146
+ const currentBlockedBy = new Set(existingTask.blockedBy || []);
147
+ input.addBlockedBy.forEach((id) => currentBlockedBy.add(id));
148
+ updatedTask.blockedBy = Array.from(currentBlockedBy);
149
+ }
150
+ const updatedTasks = [...tasks];
151
+ updatedTasks[taskIndex] = updatedTask;
152
+ saveTasks(updatedTasks);
153
+ emitReminderEvent("task:updated", {
154
+ previousTask: existingTask,
155
+ updatedTask,
156
+ timestamp: now
157
+ });
158
+ return updatedTask;
159
+ }
160
+ function getTaskById(taskId) {
161
+ const tasks = getAllTasks();
162
+ return tasks.find((t) => t.id === taskId) || null;
163
+ }
164
+ function getTaskList() {
165
+ const tasks = getAllTasks();
166
+ return tasks.map((task) => {
167
+ const openBlockers = (task.blockedBy || []).filter((blockerId) => {
168
+ const blocker = tasks.find((t) => t.id === blockerId);
169
+ return blocker && blocker.status !== "completed";
170
+ });
171
+ return {
172
+ id: task.id,
173
+ subject: task.subject,
174
+ status: task.status,
175
+ owner: task.owner,
176
+ blockedBy: openBlockers.length > 0 ? openBlockers : void 0
177
+ };
178
+ });
179
+ }
180
+ function clearAllTasks() {
181
+ saveTasks([]);
182
+ nextTaskId = 1;
183
+ emitReminderEvent("task:cleared", {
184
+ timestamp: Date.now()
185
+ });
186
+ }
187
+ function getTaskStatistics() {
188
+ const tasks = getAllTasks();
189
+ return {
190
+ total: tasks.length,
191
+ pending: tasks.filter((t) => t.status === "pending").length,
192
+ inProgress: tasks.filter((t) => t.status === "in_progress").length,
193
+ completed: tasks.filter((t) => t.status === "completed").length
194
+ };
195
+ }
196
+ export {
197
+ clearAllTasks,
198
+ createTask,
199
+ getAllTasks,
200
+ getTaskById,
201
+ getTaskList,
202
+ getTaskStatistics,
203
+ updateTask
204
+ };
205
+ //# sourceMappingURL=taskStore.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/services/taskStore.ts"],
4
+ "sourcesContent": ["/**\n * Task Store Service - Claude Code Compatible\n *\n * Provides task management functionality compatible with Claude Code's\n * TaskCreate, TaskUpdate, TaskGet, TaskList tools.\n *\n * Task lifecycle: pending \u2192 in_progress \u2192 completed\n * Tasks can also be deleted (soft delete with 'deleted' status)\n */\n\nimport { getSessionState, setSessionState } from '@utils/sessionState'\nimport { emitReminderEvent } from '@services/systemReminder'\n\n/**\n * Task interface matching Claude Code's task structure\n */\nexport interface Task {\n id: string\n subject: string\n description: string\n status: 'pending' | 'in_progress' | 'completed'\n activeForm?: string\n owner?: string\n metadata?: Record<string, unknown>\n blocks?: string[] // Task IDs that this task blocks\n blockedBy?: string[] // Task IDs that block this task\n createdAt: number\n updatedAt: number\n}\n\n/**\n * Task creation input (without auto-generated fields)\n */\nexport interface TaskCreateInput {\n subject: string\n description: string\n activeForm?: string\n metadata?: Record<string, unknown>\n}\n\n/**\n * Task update input (all fields optional except taskId)\n */\nexport interface TaskUpdateInput {\n taskId: string\n status?: 'pending' | 'in_progress' | 'completed' | 'deleted'\n subject?: string\n description?: string\n activeForm?: string\n owner?: string\n metadata?: Record<string, unknown>\n addBlocks?: string[]\n addBlockedBy?: string[]\n}\n\nconst TASK_STORAGE_KEY = 'claudeCodeTasks'\n\n// In-memory cache\nlet taskCache: Task[] | null = null\nlet cacheTimestamp = 0\nconst CACHE_TTL = 5000 // 5 seconds\n\n// Auto-incrementing ID counter\nlet nextTaskId = 1\n\nfunction invalidateCache(): void {\n taskCache = null\n cacheTimestamp = 0\n}\n\nfunction getNextTaskId(): string {\n // Find the highest existing ID to avoid conflicts\n const tasks = getAllTasks()\n const existingIds = tasks\n .map(t => parseInt(t.id, 10))\n .filter(id => !isNaN(id))\n\n if (existingIds.length > 0) {\n nextTaskId = Math.max(...existingIds) + 1\n }\n\n return String(nextTaskId++)\n}\n\n/**\n * Generate activeForm from subject if not provided\n * Converts imperative form to present continuous\n */\nfunction generateActiveForm(subject: string): string {\n const trimmed = subject.trim()\n\n const patterns = [\n { regex: /^(Run|run)\\s+(.+)$/i, replacement: 'Running $2' },\n { regex: /^(Build|build)\\s+(.+)$/i, replacement: 'Building $2' },\n { regex: /^(Fix|fix)\\s+(.+)$/i, replacement: 'Fixing $2' },\n { regex: /^(Add|add)\\s+(.+)$/i, replacement: 'Adding $2' },\n { regex: /^(Create|create)\\s+(.+)$/i, replacement: 'Creating $2' },\n { regex: /^(Update|update)\\s+(.+)$/i, replacement: 'Updating $2' },\n { regex: /^(Delete|delete)\\s+(.+)$/i, replacement: 'Deleting $2' },\n { regex: /^(Test|test)\\s+(.+)$/i, replacement: 'Testing $2' },\n { regex: /^(Deploy|deploy)\\s+(.+)$/i, replacement: 'Deploying $2' },\n { regex: /^(Analyze|analyze)\\s+(.+)$/i, replacement: 'Analyzing $2' },\n { regex: /^(Review|review)\\s+(.+)$/i, replacement: 'Reviewing $2' },\n { regex: /^(Write|write)\\s+(.+)$/i, replacement: 'Writing $2' },\n {\n regex: /^(Implement|implement)\\s+(.+)$/i,\n replacement: 'Implementing $2',\n },\n { regex: /^(Refactor|refactor)\\s+(.+)$/i, replacement: 'Refactoring $2' },\n { regex: /^(Debug|debug)\\s+(.+)$/i, replacement: 'Debugging $2' },\n { regex: /^(Configure|configure)\\s+(.+)$/i, replacement: 'Configuring $2' },\n { regex: /^(Install|install)\\s+(.+)$/i, replacement: 'Installing $2' },\n { regex: /^(Remove|remove)\\s+(.+)$/i, replacement: 'Removing $2' },\n { regex: /^(Verify|verify)\\s+(.+)$/i, replacement: 'Verifying $2' },\n { regex: /^(Check|check)\\s+(.+)$/i, replacement: 'Checking $2' },\n ]\n\n for (const { regex, replacement } of patterns) {\n if (regex.test(trimmed)) {\n return trimmed.replace(regex, replacement)\n }\n }\n\n return `Working on: ${trimmed}`\n}\n\n/**\n * Get all tasks from storage\n */\nexport function getAllTasks(): Task[] {\n const now = Date.now()\n\n // Check cache first\n if (taskCache && now - cacheTimestamp < CACHE_TTL) {\n return taskCache\n }\n\n const sessionState = getSessionState() as Record<string, unknown>\n const tasks = (sessionState[TASK_STORAGE_KEY] as Task[]) || []\n\n // Update cache\n taskCache = [...tasks]\n cacheTimestamp = now\n\n return tasks\n}\n\n/**\n * Save tasks to storage\n */\nfunction saveTasks(tasks: Task[]): void {\n setSessionState({\n ...getSessionState(),\n [TASK_STORAGE_KEY]: tasks,\n } as Record<string, unknown>)\n\n invalidateCache()\n}\n\n/**\n * Create a new task\n */\nexport function createTask(input: TaskCreateInput): Task {\n const tasks = getAllTasks()\n const now = Date.now()\n\n const newTask: Task = {\n id: getNextTaskId(),\n subject: input.subject,\n description: input.description,\n status: 'pending',\n activeForm: input.activeForm || generateActiveForm(input.subject),\n metadata: input.metadata,\n createdAt: now,\n updatedAt: now,\n }\n\n const updatedTasks = [...tasks, newTask]\n saveTasks(updatedTasks)\n\n // Emit event for system reminders\n emitReminderEvent('task:created', {\n task: newTask,\n timestamp: now,\n })\n\n return newTask\n}\n\n/**\n * Update an existing task\n */\nexport function updateTask(input: TaskUpdateInput): Task | null {\n const tasks = getAllTasks()\n const taskIndex = tasks.findIndex(t => t.id === input.taskId)\n\n if (taskIndex === -1) {\n return null\n }\n\n const existingTask = tasks[taskIndex]\n const now = Date.now()\n\n // Handle deletion\n if (input.status === 'deleted') {\n const updatedTasks = tasks.filter(t => t.id !== input.taskId)\n saveTasks(updatedTasks)\n\n emitReminderEvent('task:deleted', {\n taskId: input.taskId,\n timestamp: now,\n })\n\n return existingTask\n }\n\n // Build updated task\n const updatedTask: Task = {\n ...existingTask,\n updatedAt: now,\n }\n\n if (input.status !== undefined) {\n updatedTask.status = input.status\n }\n if (input.subject !== undefined) {\n updatedTask.subject = input.subject\n }\n if (input.description !== undefined) {\n updatedTask.description = input.description\n }\n if (input.activeForm !== undefined) {\n updatedTask.activeForm = input.activeForm\n }\n if (input.owner !== undefined) {\n updatedTask.owner = input.owner\n }\n if (input.metadata !== undefined) {\n // Merge metadata, null values delete keys\n const newMetadata = { ...existingTask.metadata }\n for (const [key, value] of Object.entries(input.metadata)) {\n if (value === null) {\n delete newMetadata[key]\n } else {\n newMetadata[key] = value\n }\n }\n updatedTask.metadata =\n Object.keys(newMetadata).length > 0 ? newMetadata : undefined\n }\n\n // Handle dependency updates\n if (input.addBlocks && input.addBlocks.length > 0) {\n const currentBlocks = new Set(existingTask.blocks || [])\n input.addBlocks.forEach(id => currentBlocks.add(id))\n updatedTask.blocks = Array.from(currentBlocks)\n }\n if (input.addBlockedBy && input.addBlockedBy.length > 0) {\n const currentBlockedBy = new Set(existingTask.blockedBy || [])\n input.addBlockedBy.forEach(id => currentBlockedBy.add(id))\n updatedTask.blockedBy = Array.from(currentBlockedBy)\n }\n\n // Update tasks array\n const updatedTasks = [...tasks]\n updatedTasks[taskIndex] = updatedTask\n saveTasks(updatedTasks)\n\n // Emit event\n emitReminderEvent('task:updated', {\n previousTask: existingTask,\n updatedTask,\n timestamp: now,\n })\n\n return updatedTask\n}\n\n/**\n * Get a task by ID\n */\nexport function getTaskById(taskId: string): Task | null {\n const tasks = getAllTasks()\n return tasks.find(t => t.id === taskId) || null\n}\n\n/**\n * Get task list summary for display\n * Returns tasks with summary info suitable for listing\n */\nexport function getTaskList(): Array<{\n id: string\n subject: string\n status: 'pending' | 'in_progress' | 'completed'\n owner?: string\n blockedBy?: string[]\n}> {\n const tasks = getAllTasks()\n\n // Filter out tasks blocked by incomplete tasks\n return tasks.map(task => {\n // Check which blockedBy tasks are still open\n const openBlockers = (task.blockedBy || []).filter(blockerId => {\n const blocker = tasks.find(t => t.id === blockerId)\n return blocker && blocker.status !== 'completed'\n })\n\n return {\n id: task.id,\n subject: task.subject,\n status: task.status,\n owner: task.owner,\n blockedBy: openBlockers.length > 0 ? openBlockers : undefined,\n }\n })\n}\n\n/**\n * Clear all tasks (for testing or reset)\n */\nexport function clearAllTasks(): void {\n saveTasks([])\n nextTaskId = 1\n\n emitReminderEvent('task:cleared', {\n timestamp: Date.now(),\n })\n}\n\n/**\n * Get task statistics\n */\nexport function getTaskStatistics(): {\n total: number\n pending: number\n inProgress: number\n completed: number\n} {\n const tasks = getAllTasks()\n\n return {\n total: tasks.length,\n pending: tasks.filter(t => t.status === 'pending').length,\n inProgress: tasks.filter(t => t.status === 'in_progress').length,\n completed: tasks.filter(t => t.status === 'completed').length,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["updatedTasks"]
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
  },