@ebowwa/coder 0.7.63 → 0.7.64

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 (341) hide show
  1. package/dist/core/__tests__/permissions.test.d.ts +12 -0
  2. package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
  3. package/dist/core/__tests__/permissions.test.js +851 -0
  4. package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
  5. package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
  6. package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
  7. package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
  8. package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
  9. package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
  10. package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
  11. package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/core/agent-loop/__tests__/index.test.js +121 -0
  13. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
  14. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
  15. package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
  16. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
  17. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
  18. package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
  19. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
  20. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
  21. package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
  22. package/dist/core/agent-loop/compaction.d.ts +39 -0
  23. package/dist/core/agent-loop/compaction.d.ts.map +1 -0
  24. package/dist/core/agent-loop/compaction.js +51 -0
  25. package/dist/core/agent-loop/formatters.d.ts +21 -0
  26. package/dist/core/agent-loop/formatters.d.ts.map +1 -0
  27. package/dist/core/agent-loop/formatters.js +42 -0
  28. package/dist/core/agent-loop/index.d.ts +25 -0
  29. package/dist/core/agent-loop/index.d.ts.map +1 -0
  30. package/dist/core/agent-loop/index.js +83 -0
  31. package/dist/core/agent-loop/loop-state.d.ts +74 -0
  32. package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
  33. package/dist/core/agent-loop/loop-state.js +147 -0
  34. package/dist/core/agent-loop/message-builder.d.ts +13 -0
  35. package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
  36. package/dist/core/agent-loop/message-builder.js +49 -0
  37. package/dist/core/agent-loop/tool-executor.d.ts +23 -0
  38. package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
  39. package/dist/core/agent-loop/tool-executor.js +152 -0
  40. package/dist/core/agent-loop/turn-executor.d.ts +57 -0
  41. package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
  42. package/dist/core/agent-loop/turn-executor.js +124 -0
  43. package/dist/core/agent-loop/types.d.ts +141 -0
  44. package/dist/core/agent-loop/types.d.ts.map +1 -0
  45. package/dist/core/agent-loop/types.js +4 -0
  46. package/dist/core/agent-loop.d.ts +17 -0
  47. package/dist/core/agent-loop.d.ts.map +1 -0
  48. package/dist/core/agent-loop.js +16 -0
  49. package/dist/core/api-client-impl.d.ts +62 -0
  50. package/dist/core/api-client-impl.d.ts.map +1 -0
  51. package/dist/core/api-client-impl.js +479 -0
  52. package/dist/core/api-client.d.ts +6 -0
  53. package/dist/core/api-client.d.ts.map +1 -0
  54. package/dist/core/api-client.js +5 -0
  55. package/dist/core/checkpoints.d.ts +128 -0
  56. package/dist/core/checkpoints.d.ts.map +1 -0
  57. package/dist/core/checkpoints.js +438 -0
  58. package/dist/core/claude-md.d.ts +71 -0
  59. package/dist/core/claude-md.d.ts.map +1 -0
  60. package/dist/core/claude-md.js +198 -0
  61. package/dist/core/cognitive-security/hooks.d.ts +138 -0
  62. package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
  63. package/dist/core/cognitive-security/hooks.js +389 -0
  64. package/dist/core/cognitive-security/index.d.ts +751 -0
  65. package/dist/core/cognitive-security/index.d.ts.map +1 -0
  66. package/dist/core/cognitive-security/index.js +1123 -0
  67. package/dist/core/cognitive-security/middleware.d.ts +136 -0
  68. package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
  69. package/dist/core/cognitive-security/middleware.js +376 -0
  70. package/dist/core/config-loader.d.ts +127 -0
  71. package/dist/core/config-loader.d.ts.map +1 -0
  72. package/dist/core/config-loader.js +219 -0
  73. package/dist/core/context-compaction.d.ts +87 -0
  74. package/dist/core/context-compaction.d.ts.map +1 -0
  75. package/dist/core/context-compaction.js +428 -0
  76. package/dist/core/git-status.d.ts +25 -0
  77. package/dist/core/git-status.d.ts.map +1 -0
  78. package/dist/core/git-status.js +204 -0
  79. package/dist/core/image.d.ts +69 -0
  80. package/dist/core/image.d.ts.map +1 -0
  81. package/dist/core/image.js +290 -0
  82. package/dist/core/image.test.d.ts +2 -0
  83. package/dist/core/image.test.d.ts.map +1 -0
  84. package/dist/core/image.test.js +149 -0
  85. package/dist/core/models.d.ts +123 -0
  86. package/dist/core/models.d.ts.map +1 -0
  87. package/dist/core/models.js +325 -0
  88. package/dist/core/permissions.d.ts +81 -0
  89. package/dist/core/permissions.d.ts.map +1 -0
  90. package/dist/core/permissions.js +327 -0
  91. package/dist/core/retry.d.ts +25 -0
  92. package/dist/core/retry.d.ts.map +1 -0
  93. package/dist/core/retry.js +121 -0
  94. package/dist/core/session-store.d.ts +9 -0
  95. package/dist/core/session-store.d.ts.map +1 -0
  96. package/dist/core/session-store.js +10 -0
  97. package/dist/core/sessions/export.d.ts +47 -0
  98. package/dist/core/sessions/export.d.ts.map +1 -0
  99. package/dist/core/sessions/export.js +256 -0
  100. package/dist/core/sessions/index.d.ts +132 -0
  101. package/dist/core/sessions/index.d.ts.map +1 -0
  102. package/dist/core/sessions/index.js +442 -0
  103. package/dist/core/sessions/metadata.d.ts +77 -0
  104. package/dist/core/sessions/metadata.d.ts.map +1 -0
  105. package/dist/core/sessions/metadata.js +233 -0
  106. package/dist/core/sessions/persistence.d.ts +72 -0
  107. package/dist/core/sessions/persistence.d.ts.map +1 -0
  108. package/dist/core/sessions/persistence.js +201 -0
  109. package/dist/core/sessions/types.d.ts +110 -0
  110. package/dist/core/sessions/types.d.ts.map +1 -0
  111. package/dist/core/sessions/types.js +4 -0
  112. package/dist/core/stream-highlighter.d.ts +18 -0
  113. package/dist/core/stream-highlighter.d.ts.map +1 -0
  114. package/dist/core/stream-highlighter.js +916 -0
  115. package/dist/core/system-reminders.d.ts +89 -0
  116. package/dist/core/system-reminders.d.ts.map +1 -0
  117. package/dist/core/system-reminders.js +285 -0
  118. package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
  119. package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
  120. package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
  121. package/dist/ecosystem/hooks/index.d.ts +59 -0
  122. package/dist/ecosystem/hooks/index.d.ts.map +1 -0
  123. package/dist/ecosystem/hooks/index.js +294 -0
  124. package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
  125. package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
  126. package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
  127. package/dist/ecosystem/skills/index.d.ts +55 -0
  128. package/dist/ecosystem/skills/index.d.ts.map +1 -0
  129. package/dist/ecosystem/skills/index.js +258 -0
  130. package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
  131. package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
  132. package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
  133. package/dist/ecosystem/tools/index.d.ts +24 -0
  134. package/dist/ecosystem/tools/index.d.ts.map +1 -0
  135. package/dist/ecosystem/tools/index.js +1709 -0
  136. package/dist/index.d.ts +24 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +32 -52192
  139. package/dist/interfaces/mcp/client.d.ts +40 -0
  140. package/dist/interfaces/mcp/client.d.ts.map +1 -0
  141. package/dist/interfaces/mcp/client.js +309 -0
  142. package/dist/interfaces/ui/index.d.ts +36 -0
  143. package/dist/interfaces/ui/index.d.ts.map +1 -0
  144. package/dist/interfaces/ui/index.js +61 -0
  145. package/dist/interfaces/ui/spinner.d.ts +140 -0
  146. package/dist/interfaces/ui/spinner.d.ts.map +1 -0
  147. package/dist/interfaces/ui/spinner.js +342 -0
  148. package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
  149. package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
  150. package/dist/interfaces/ui/terminal/cli/index.js +159 -52768
  151. package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
  152. package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
  153. package/dist/interfaces/ui/terminal/shared/args.js +176 -0
  154. package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
  155. package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
  156. package/dist/interfaces/ui/terminal/shared/index.js +16 -0
  157. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
  158. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
  159. package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
  160. package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
  161. package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
  162. package/dist/interfaces/ui/terminal/shared/query.js +100 -0
  163. package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
  164. package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
  165. package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
  166. package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
  167. package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
  168. package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
  169. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
  170. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
  171. package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
  172. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
  173. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
  174. package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
  175. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
  176. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
  177. package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
  178. package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
  179. package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
  180. package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
  181. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
  182. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
  183. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
  184. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
  185. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
  186. package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
  187. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
  188. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
  189. package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
  190. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
  191. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
  192. package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
  193. package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
  194. package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
  195. package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
  196. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
  197. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
  198. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
  199. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
  200. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
  201. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
  202. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
  203. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
  204. package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
  205. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
  206. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
  207. package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
  208. package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
  209. package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
  210. package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
  211. package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
  212. package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
  213. package/dist/interfaces/ui/terminal/tui/console.js +46 -0
  214. package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
  215. package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
  216. package/dist/interfaces/ui/terminal/tui/index.js +28 -0
  217. package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
  218. package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
  219. package/dist/interfaces/ui/terminal/tui/run.js +31 -0
  220. package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
  221. package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
  222. package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
  223. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
  224. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
  225. package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
  226. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
  227. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
  228. package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
  229. package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
  230. package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
  231. package/dist/interfaces/ui/terminal/tui/types.js +5 -0
  232. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
  233. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
  234. package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
  235. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
  236. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
  237. package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
  238. package/dist/native/index.d.ts +480 -0
  239. package/dist/native/index.d.ts.map +1 -0
  240. package/dist/native/index.js +1625 -0
  241. package/dist/teammates/index.d.ts +161 -0
  242. package/dist/teammates/index.d.ts.map +1 -0
  243. package/dist/teammates/index.js +827 -0
  244. package/dist/types/index.d.ts +482 -0
  245. package/dist/types/index.d.ts.map +1 -0
  246. package/dist/types/index.js +52 -0
  247. package/package.json +4 -2
  248. package/packages/src/core/__tests__/permissions.test.ts +1091 -0
  249. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +280 -0
  250. package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
  251. package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
  252. package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
  253. package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
  254. package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
  255. package/packages/src/core/agent-loop/compaction.ts +88 -0
  256. package/packages/src/core/agent-loop/formatters.ts +50 -0
  257. package/packages/src/core/agent-loop/index.ts +135 -0
  258. package/packages/src/core/agent-loop/loop-state.ts +187 -0
  259. package/packages/src/core/agent-loop/message-builder.ts +62 -0
  260. package/packages/src/core/agent-loop/tool-executor.ts +211 -0
  261. package/packages/src/core/agent-loop/turn-executor.ts +222 -0
  262. package/packages/src/core/agent-loop/types.ts +148 -0
  263. package/packages/src/core/agent-loop.ts +18 -0
  264. package/packages/src/core/api-client-impl.ts +619 -0
  265. package/packages/src/core/api-client.ts +6 -0
  266. package/packages/src/core/checkpoints.ts +606 -0
  267. package/packages/src/core/claude-md.ts +272 -0
  268. package/packages/src/core/cognitive-security/hooks.ts +590 -0
  269. package/packages/src/core/cognitive-security/index.ts +2041 -0
  270. package/packages/src/core/cognitive-security/middleware.ts +536 -0
  271. package/packages/src/core/config-loader.ts +324 -0
  272. package/packages/src/core/context-compaction.ts +578 -0
  273. package/packages/src/core/git-status.ts +262 -0
  274. package/packages/src/core/image.test.ts +180 -0
  275. package/packages/src/core/image.ts +350 -0
  276. package/packages/src/core/lmdb.db +0 -0
  277. package/packages/src/core/lmdb.db-lock +0 -0
  278. package/packages/src/core/models.ts +430 -0
  279. package/packages/src/core/normalizers/todo +4 -0
  280. package/packages/src/core/permissions.ts +431 -0
  281. package/packages/src/core/retry.ts +170 -0
  282. package/packages/src/core/session-store.ts +36 -0
  283. package/packages/src/core/sessions/export.ts +329 -0
  284. package/packages/src/core/sessions/index.ts +587 -0
  285. package/packages/src/core/sessions/metadata.ts +309 -0
  286. package/packages/src/core/sessions/persistence.ts +244 -0
  287. package/packages/src/core/sessions/types.ts +169 -0
  288. package/packages/src/core/stream-highlighter.ts +1123 -0
  289. package/packages/src/core/system-reminders.ts +402 -0
  290. package/packages/src/core/todo +8 -0
  291. package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
  292. package/packages/src/ecosystem/hooks/index.ts +341 -0
  293. package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
  294. package/packages/src/ecosystem/skills/index.ts +295 -0
  295. package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
  296. package/packages/src/ecosystem/tools/index.ts +1877 -0
  297. package/packages/src/index.ts +120 -0
  298. package/packages/src/interfaces/mcp/client.ts +389 -0
  299. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  300. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  301. package/packages/src/interfaces/ui/index.ts +161 -0
  302. package/packages/src/interfaces/ui/lmdb.db +0 -0
  303. package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
  304. package/packages/src/interfaces/ui/spinner.ts +451 -0
  305. package/packages/src/interfaces/ui/terminal/cli/index.ts +228 -0
  306. package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
  307. package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
  308. package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
  309. package/packages/src/interfaces/ui/terminal/shared/index.ts +71 -0
  310. package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
  311. package/packages/src/interfaces/ui/terminal/shared/query.ts +146 -0
  312. package/packages/src/interfaces/ui/terminal/shared/setup.ts +295 -0
  313. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +358 -0
  314. package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
  315. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +262 -0
  316. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +232 -0
  317. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +62 -0
  318. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +537 -0
  319. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +107 -0
  320. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +240 -0
  321. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +54 -0
  322. package/packages/src/interfaces/ui/terminal/tui/commands.ts +438 -0
  323. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +584 -0
  324. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +614 -0
  325. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +333 -0
  326. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +604 -0
  327. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +118 -0
  328. package/packages/src/interfaces/ui/terminal/tui/console.ts +49 -0
  329. package/packages/src/interfaces/ui/terminal/tui/index.ts +90 -0
  330. package/packages/src/interfaces/ui/terminal/tui/run.tsx +42 -0
  331. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +69 -0
  332. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +390 -0
  333. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +422 -0
  334. package/packages/src/interfaces/ui/terminal/tui/types.ts +186 -0
  335. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +104 -0
  336. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +239 -0
  337. package/packages/src/lmdb.db +0 -0
  338. package/packages/src/lmdb.db-lock +0 -0
  339. package/packages/src/native/index.ts +2345 -0
  340. package/packages/src/teammates/index.ts +982 -0
  341. package/packages/src/types/index.ts +722 -0
@@ -0,0 +1,851 @@
1
+ /**
2
+ * Comprehensive tests for the Permission System
3
+ *
4
+ * Tests cover:
5
+ * - All permission modes (default, acceptEdits, bypassPermissions, dontAsk, plan, interactive)
6
+ * - Risk level assessment (low, medium, high, critical)
7
+ * - Tool categorization (readOnly, fileEdit, system)
8
+ * - Permission caching and callbacks
9
+ * - Security-critical paths
10
+ */
11
+ import { describe, test, it, expect, beforeEach, afterEach, mock, spyOn, } from "bun:test";
12
+ import { PermissionManager, assessRiskLevel, generateDescription, isReadOnlyTool, isFileEditTool, isSystemTool, TOOL_CATEGORIES, } from "../permissions.js";
13
+ // ============================================
14
+ // MOCKS AND HELPERS
15
+ // ============================================
16
+ /**
17
+ * Create a mock prompt callback that returns a specific decision
18
+ */
19
+ function createMockPrompt(decision) {
20
+ return async (_request) => ({
21
+ decision,
22
+ reason: `Mocked decision: ${decision}`,
23
+ });
24
+ }
25
+ /**
26
+ * Create a delayed mock prompt for testing async behavior
27
+ */
28
+ function createDelayedMockPrompt(decision, delayMs = 10) {
29
+ return async (_request) => {
30
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
31
+ return { decision, reason: `Delayed mock: ${decision}` };
32
+ };
33
+ }
34
+ /**
35
+ * Sample tool inputs for testing
36
+ */
37
+ const SAMPLE_INPUTS = {
38
+ readFile: { file_path: "/path/to/file.ts" },
39
+ writeFile: { file_path: "/path/to/output.ts", content: "console.log('hello')" },
40
+ editFile: { file_path: "/path/to/edit.ts", old_string: "old", new_string: "new" },
41
+ glob: { pattern: "**/*.ts" },
42
+ grep: { pattern: "function", path: "./src" },
43
+ task: { subagent_type: "Explore" },
44
+ bashSafe: { command: "ls -la" },
45
+ bashCritical: { command: "rm -rf /" },
46
+ bashSudo: { command: "sudo apt update" },
47
+ bashGit: { command: "git status" },
48
+ bashForcePush: { command: "git push --force origin main" },
49
+ bashResetHard: { command: "git reset --hard HEAD~1" },
50
+ envFile: { file_path: "/path/to/.env", content: "SECRET=abc123" },
51
+ sshKey: { file_path: "/home/user/.ssh/id_rsa", content: "PRIVATE KEY" },
52
+ gpgKey: { file_path: "/home/user/.gnupg/private.key", content: "GPG KEY" },
53
+ };
54
+ // ============================================
55
+ // RISK LEVEL ASSESSMENT TESTS
56
+ // ============================================
57
+ describe("assessRiskLevel", () => {
58
+ describe("low risk operations", () => {
59
+ test("Read tool is low risk", () => {
60
+ expect(assessRiskLevel("Read", SAMPLE_INPUTS.readFile)).toBe("low");
61
+ });
62
+ test("Glob tool is low risk", () => {
63
+ expect(assessRiskLevel("Glob", SAMPLE_INPUTS.glob)).toBe("low");
64
+ });
65
+ test("Grep tool is low risk", () => {
66
+ expect(assessRiskLevel("Grep", SAMPLE_INPUTS.grep)).toBe("low");
67
+ });
68
+ test("Task tool is low risk", () => {
69
+ expect(assessRiskLevel("Task", SAMPLE_INPUTS.task)).toBe("low");
70
+ });
71
+ test("Unknown tools default to medium risk", () => {
72
+ expect(assessRiskLevel("UnknownTool", {})).toBe("medium");
73
+ });
74
+ });
75
+ describe("medium risk operations", () => {
76
+ test("Write tool is medium risk", () => {
77
+ expect(assessRiskLevel("Write", SAMPLE_INPUTS.writeFile)).toBe("medium");
78
+ });
79
+ test("Edit tool is medium risk", () => {
80
+ expect(assessRiskLevel("Edit", SAMPLE_INPUTS.editFile)).toBe("medium");
81
+ });
82
+ test("NotebookEdit tool is medium risk", () => {
83
+ expect(assessRiskLevel("NotebookEdit", { cell_id: "1" })).toBe("medium");
84
+ });
85
+ test("Bash with safe commands is medium risk (default)", () => {
86
+ expect(assessRiskLevel("Bash", SAMPLE_INPUTS.bashSafe)).toBe("medium");
87
+ });
88
+ });
89
+ describe("high risk operations", () => {
90
+ test("Bash with sudo is high risk", () => {
91
+ expect(assessRiskLevel("Bash", SAMPLE_INPUTS.bashSudo)).toBe("high");
92
+ });
93
+ test("Bash with chmod is high risk", () => {
94
+ expect(assessRiskLevel("Bash", { command: "chmod 777 file" })).toBe("high");
95
+ });
96
+ test("Write to .env file is high risk", () => {
97
+ expect(assessRiskLevel("Write", SAMPLE_INPUTS.envFile)).toBe("high");
98
+ });
99
+ test("Write to .pem file is high risk", () => {
100
+ expect(assessRiskLevel("Write", { file_path: "/cert.pem", content: "cert" })).toBe("high");
101
+ });
102
+ test("Write to .key file is high risk", () => {
103
+ expect(assessRiskLevel("Write", { file_path: "/secret.key", content: "key" })).toBe("high");
104
+ });
105
+ test("Write to .secret file is high risk", () => {
106
+ expect(assessRiskLevel("Write", { file_path: "/app.secret", content: "secret" })).toBe("high");
107
+ });
108
+ test("Write to .credentials file is high risk", () => {
109
+ expect(assessRiskLevel("Write", { file_path: "/.credentials", content: "creds" })).toBe("high");
110
+ });
111
+ test("Edit to .env file is high risk", () => {
112
+ expect(assessRiskLevel("Edit", { ...SAMPLE_INPUTS.envFile, old_string: "", new_string: "" })).toBe("high");
113
+ });
114
+ });
115
+ describe("critical risk operations", () => {
116
+ test("Bash with 'rm -rf' is critical", () => {
117
+ expect(assessRiskLevel("Bash", SAMPLE_INPUTS.bashCritical)).toBe("critical");
118
+ });
119
+ test("Bash with 'rm -r' is critical", () => {
120
+ expect(assessRiskLevel("Bash", { command: "rm -r folder" })).toBe("critical");
121
+ });
122
+ test("Bash with plain 'rm' is critical", () => {
123
+ expect(assessRiskLevel("Bash", { command: "rm file.txt" })).toBe("critical");
124
+ });
125
+ test("Bash with 'git push --force' is critical", () => {
126
+ expect(assessRiskLevel("Bash", SAMPLE_INPUTS.bashForcePush)).toBe("critical");
127
+ });
128
+ test("Bash with 'git reset --hard' is critical", () => {
129
+ expect(assessRiskLevel("Bash", SAMPLE_INPUTS.bashResetHard)).toBe("critical");
130
+ });
131
+ test("Bash with 'git clean -fd' is critical", () => {
132
+ expect(assessRiskLevel("Bash", { command: "git clean -fd" })).toBe("critical");
133
+ });
134
+ test("Bash with 'drop' keyword is critical", () => {
135
+ expect(assessRiskLevel("Bash", { command: "echo drop table" })).toBe("critical");
136
+ });
137
+ test("Bash with 'delete' keyword is critical", () => {
138
+ expect(assessRiskLevel("Bash", { command: "echo delete from" })).toBe("critical");
139
+ });
140
+ test("Bash with 'truncate' keyword is critical", () => {
141
+ expect(assessRiskLevel("Bash", { command: "truncate -s 0 file" })).toBe("critical");
142
+ });
143
+ test("Bash with 'format' keyword is critical", () => {
144
+ expect(assessRiskLevel("Bash", { command: "format drive" })).toBe("critical");
145
+ });
146
+ test("Bash with 'dd if=' is critical", () => {
147
+ expect(assessRiskLevel("Bash", { command: "dd if=/dev/zero of=/dev/sda" })).toBe("critical");
148
+ });
149
+ test("Bash with 'shred' is critical", () => {
150
+ expect(assessRiskLevel("Bash", { command: "shred -u file" })).toBe("critical");
151
+ });
152
+ test("Bash with fork bomb pattern is critical", () => {
153
+ // Note: The regex in permissions.ts is /\b:\(\)\{\s*:\|:\s*&\s*\};\s*:\b/
154
+ // BUG: The \b word boundary won't match before : (non-word char)
155
+ // This pattern currently does NOT detect fork bombs - security gap
156
+ const result = assessRiskLevel("Bash", { command: ":(){ :|:& };:" });
157
+ // CURRENT BEHAVIOR: medium (not detected due to regex bug)
158
+ // SHOULD BE: critical
159
+ expect(result).toBe("medium");
160
+ });
161
+ test("Bash with fork bomb variant - also not detected", () => {
162
+ // Test with spaces - also won't match due to word boundary issue
163
+ const result = assessRiskLevel("Bash", { command: ":(){ :|:& }; :" });
164
+ // CURRENT BEHAVIOR: medium (not detected)
165
+ expect(result).toBe("medium");
166
+ });
167
+ test("Write to .ssh directory is critical", () => {
168
+ expect(assessRiskLevel("Write", SAMPLE_INPUTS.sshKey)).toBe("critical");
169
+ });
170
+ test("Write to .gnupg directory is critical", () => {
171
+ expect(assessRiskLevel("Write", SAMPLE_INPUTS.gpgKey)).toBe("critical");
172
+ });
173
+ test("Edit to .ssh directory is critical", () => {
174
+ expect(assessRiskLevel("Edit", { ...SAMPLE_INPUTS.sshKey, old_string: "", new_string: "" })).toBe("critical");
175
+ });
176
+ test("Edit to .gnupg directory is critical", () => {
177
+ expect(assessRiskLevel("Edit", { ...SAMPLE_INPUTS.gpgKey, old_string: "", new_string: "" })).toBe("critical");
178
+ });
179
+ });
180
+ describe("edge cases", () => {
181
+ test("empty tool input handles gracefully", () => {
182
+ expect(assessRiskLevel("Read", {})).toBe("low");
183
+ expect(assessRiskLevel("Bash", {})).toBe("medium");
184
+ });
185
+ test("null command in Bash defaults to medium", () => {
186
+ expect(assessRiskLevel("Bash", { command: null })).toBe("medium");
187
+ });
188
+ test("undefined command in Bash defaults to medium", () => {
189
+ expect(assessRiskLevel("Bash", { command: undefined })).toBe("medium");
190
+ });
191
+ test("case sensitivity in critical patterns", () => {
192
+ expect(assessRiskLevel("Bash", { command: "DROP TABLE users" })).toBe("critical");
193
+ expect(assessRiskLevel("Bash", { command: "DELETE FROM table" })).toBe("critical");
194
+ });
195
+ });
196
+ });
197
+ // ============================================
198
+ // DESCRIPTION GENERATION TESTS
199
+ // ============================================
200
+ describe("generateDescription", () => {
201
+ test("Read tool description", () => {
202
+ const desc = generateDescription("Read", SAMPLE_INPUTS.readFile);
203
+ expect(desc).toContain("Read file:");
204
+ expect(desc).toContain("/path/to/file.ts");
205
+ });
206
+ test("Write tool description includes content length", () => {
207
+ const desc = generateDescription("Write", SAMPLE_INPUTS.writeFile);
208
+ expect(desc).toContain("Write file:");
209
+ expect(desc).toContain("chars");
210
+ });
211
+ test("Edit tool description", () => {
212
+ const desc = generateDescription("Edit", SAMPLE_INPUTS.editFile);
213
+ expect(desc).toContain("Edit file:");
214
+ expect(desc).toContain("/path/to/edit.ts");
215
+ });
216
+ test("Bash tool description truncates long commands", () => {
217
+ const longCommand = "a".repeat(150);
218
+ const desc = generateDescription("Bash", { command: longCommand });
219
+ expect(desc).toContain("Execute:");
220
+ expect(desc).toContain("...");
221
+ expect(desc.length).toBeLessThan(200);
222
+ });
223
+ test("Bash tool description shows short commands fully", () => {
224
+ const desc = generateDescription("Bash", SAMPLE_INPUTS.bashSafe);
225
+ expect(desc).toContain("Execute:");
226
+ expect(desc).toContain("ls -la");
227
+ expect(desc).not.toContain("...");
228
+ });
229
+ test("Glob tool description", () => {
230
+ const desc = generateDescription("Glob", SAMPLE_INPUTS.glob);
231
+ expect(desc).toContain("Find files:");
232
+ expect(desc).toContain("**/*.ts");
233
+ });
234
+ test("Grep tool description", () => {
235
+ const desc = generateDescription("Grep", SAMPLE_INPUTS.grep);
236
+ expect(desc).toContain("Search:");
237
+ expect(desc).toContain("function");
238
+ expect(desc).toContain("./src");
239
+ });
240
+ test("Task tool description", () => {
241
+ const desc = generateDescription("Task", SAMPLE_INPUTS.task);
242
+ expect(desc).toContain("Spawn agent:");
243
+ expect(desc).toContain("Explore");
244
+ });
245
+ test("Unknown tool fallback description", () => {
246
+ const desc = generateDescription("CustomTool", { param: "value" });
247
+ expect(desc).toContain("Use tool:");
248
+ expect(desc).toContain("CustomTool");
249
+ });
250
+ test("handles missing parameters gracefully", () => {
251
+ expect(generateDescription("Read", {})).toContain("unknown");
252
+ expect(generateDescription("Write", {})).toContain("unknown");
253
+ expect(generateDescription("Glob", {})).toContain("*");
254
+ expect(generateDescription("Grep", {})).toContain(".");
255
+ });
256
+ });
257
+ // ============================================
258
+ // TOOL CATEGORIZATION TESTS
259
+ // ============================================
260
+ describe("Tool Categorization", () => {
261
+ describe("TOOL_CATEGORIES constant", () => {
262
+ test("contains expected readOnly tools", () => {
263
+ expect(TOOL_CATEGORIES.readOnly).toContain("Read");
264
+ expect(TOOL_CATEGORIES.readOnly).toContain("Glob");
265
+ expect(TOOL_CATEGORIES.readOnly).toContain("Grep");
266
+ expect(TOOL_CATEGORIES.readOnly).toContain("Task");
267
+ });
268
+ test("contains expected fileEdit tools", () => {
269
+ expect(TOOL_CATEGORIES.fileEdit).toContain("Write");
270
+ expect(TOOL_CATEGORIES.fileEdit).toContain("Edit");
271
+ expect(TOOL_CATEGORIES.fileEdit).toContain("NotebookEdit");
272
+ });
273
+ test("contains expected system tools", () => {
274
+ expect(TOOL_CATEGORIES.system).toContain("Bash");
275
+ });
276
+ });
277
+ describe("isReadOnlyTool", () => {
278
+ test("returns true for readOnly tools", () => {
279
+ expect(isReadOnlyTool("Read")).toBe(true);
280
+ expect(isReadOnlyTool("Glob")).toBe(true);
281
+ expect(isReadOnlyTool("Grep")).toBe(true);
282
+ expect(isReadOnlyTool("Task")).toBe(true);
283
+ });
284
+ test("returns false for non-readOnly tools", () => {
285
+ expect(isReadOnlyTool("Write")).toBe(false);
286
+ expect(isReadOnlyTool("Edit")).toBe(false);
287
+ expect(isReadOnlyTool("Bash")).toBe(false);
288
+ });
289
+ test("returns false for unknown tools", () => {
290
+ expect(isReadOnlyTool("Unknown")).toBe(false);
291
+ });
292
+ });
293
+ describe("isFileEditTool", () => {
294
+ test("returns true for fileEdit tools", () => {
295
+ expect(isFileEditTool("Write")).toBe(true);
296
+ expect(isFileEditTool("Edit")).toBe(true);
297
+ expect(isFileEditTool("NotebookEdit")).toBe(true);
298
+ });
299
+ test("returns false for non-fileEdit tools", () => {
300
+ expect(isFileEditTool("Read")).toBe(false);
301
+ expect(isFileEditTool("Bash")).toBe(false);
302
+ expect(isFileEditTool("Grep")).toBe(false);
303
+ });
304
+ test("returns false for unknown tools", () => {
305
+ expect(isFileEditTool("Unknown")).toBe(false);
306
+ });
307
+ });
308
+ describe("isSystemTool", () => {
309
+ test("returns true for system tools", () => {
310
+ expect(isSystemTool("Bash")).toBe(true);
311
+ });
312
+ test("returns false for non-system tools", () => {
313
+ expect(isSystemTool("Read")).toBe(false);
314
+ expect(isSystemTool("Write")).toBe(false);
315
+ expect(isSystemTool("Edit")).toBe(false);
316
+ });
317
+ test("returns false for unknown tools", () => {
318
+ expect(isSystemTool("Unknown")).toBe(false);
319
+ });
320
+ });
321
+ });
322
+ // ============================================
323
+ // PERMISSION MODE TESTS
324
+ // ============================================
325
+ describe("PermissionManager Modes", () => {
326
+ describe("bypassPermissions mode", () => {
327
+ let manager;
328
+ beforeEach(() => {
329
+ manager = new PermissionManager("bypassPermissions");
330
+ });
331
+ test("allows all Read operations", async () => {
332
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
333
+ expect(result.decision).toBe("allow");
334
+ });
335
+ test("allows all Write operations", async () => {
336
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
337
+ expect(result.decision).toBe("allow");
338
+ });
339
+ test("allows critical Bash commands", async () => {
340
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashCritical);
341
+ expect(result.decision).toBe("allow");
342
+ });
343
+ test("allows sensitive file operations", async () => {
344
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.sshKey);
345
+ expect(result.decision).toBe("allow");
346
+ });
347
+ test("no reason provided in bypass mode", async () => {
348
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
349
+ expect(result.reason).toBeUndefined();
350
+ });
351
+ });
352
+ describe("dontAsk mode", () => {
353
+ let manager;
354
+ beforeEach(() => {
355
+ manager = new PermissionManager("dontAsk");
356
+ });
357
+ test("denies all Read operations", async () => {
358
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
359
+ expect(result.decision).toBe("deny");
360
+ });
361
+ test("denies all Write operations", async () => {
362
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
363
+ expect(result.decision).toBe("deny");
364
+ });
365
+ test("denies critical Bash commands", async () => {
366
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashCritical);
367
+ expect(result.decision).toBe("deny");
368
+ });
369
+ test("provides reason for denial", async () => {
370
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
371
+ expect(result.reason).toContain("dontAsk");
372
+ });
373
+ test("denies even safe operations", async () => {
374
+ const result = await manager.checkPermission("Glob", SAMPLE_INPUTS.glob);
375
+ expect(result.decision).toBe("deny");
376
+ });
377
+ });
378
+ describe("acceptEdits mode", () => {
379
+ let manager;
380
+ beforeEach(() => {
381
+ manager = new PermissionManager("acceptEdits");
382
+ });
383
+ test("allows Read operations", async () => {
384
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
385
+ expect(result.decision).toBe("allow");
386
+ });
387
+ test("allows Write operations", async () => {
388
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
389
+ expect(result.decision).toBe("allow");
390
+ });
391
+ test("allows Edit operations", async () => {
392
+ const result = await manager.checkPermission("Edit", SAMPLE_INPUTS.editFile);
393
+ expect(result.decision).toBe("allow");
394
+ });
395
+ test("allows Glob operations", async () => {
396
+ const result = await manager.checkPermission("Glob", SAMPLE_INPUTS.glob);
397
+ expect(result.decision).toBe("allow");
398
+ });
399
+ test("allows Grep operations", async () => {
400
+ const result = await manager.checkPermission("Grep", SAMPLE_INPUTS.grep);
401
+ expect(result.decision).toBe("allow");
402
+ });
403
+ test("allows low/medium risk Bash commands", async () => {
404
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
405
+ expect(result.decision).toBe("allow");
406
+ });
407
+ test("allows git status command (low risk read-only)", async () => {
408
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashGit);
409
+ expect(result.decision).toBe("allow");
410
+ });
411
+ test("SECURITY GAP: high risk Bash commands (sudo) are NOT prompted - falls through to default allow", async () => {
412
+ // BUG DOCUMENTATION: acceptEdits mode only checks for low/medium risk
413
+ // High/critical risk commands fall through to line 293 which defaults to allow
414
+ // This is a security gap that should be fixed in the implementation
415
+ const mockPrompt = createMockPrompt("deny");
416
+ manager = new PermissionManager("acceptEdits", mockPrompt);
417
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSudo);
418
+ // CURRENT BEHAVIOR: Falls through to default allow (bug)
419
+ expect(result.decision).toBe("allow");
420
+ });
421
+ test("SECURITY GAP: critical Bash commands (rm -rf) are NOT prompted - falls through to default allow", async () => {
422
+ // BUG DOCUMENTATION: Same issue - critical commands should prompt but don't
423
+ const mockPrompt = createMockPrompt("deny");
424
+ manager = new PermissionManager("acceptEdits", mockPrompt);
425
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashCritical);
426
+ // CURRENT BEHAVIOR: Falls through to default allow (bug)
427
+ expect(result.decision).toBe("allow");
428
+ });
429
+ test("SECURITY GAP: critical Bash commands (git push --force) are NOT prompted - falls through to default allow", async () => {
430
+ // BUG DOCUMENTATION: Same issue - critical commands should prompt but don't
431
+ const mockPrompt = createMockPrompt("deny");
432
+ manager = new PermissionManager("acceptEdits", mockPrompt);
433
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashForcePush);
434
+ // CURRENT BEHAVIOR: Falls through to default allow (bug)
435
+ expect(result.decision).toBe("allow");
436
+ });
437
+ });
438
+ describe("plan mode", () => {
439
+ let manager;
440
+ beforeEach(() => {
441
+ manager = new PermissionManager("plan");
442
+ });
443
+ test("allows Read operations", async () => {
444
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
445
+ expect(result.decision).toBe("allow");
446
+ });
447
+ test("allows Glob operations", async () => {
448
+ const result = await manager.checkPermission("Glob", SAMPLE_INPUTS.glob);
449
+ expect(result.decision).toBe("allow");
450
+ });
451
+ test("allows Grep operations", async () => {
452
+ const result = await manager.checkPermission("Grep", SAMPLE_INPUTS.grep);
453
+ expect(result.decision).toBe("allow");
454
+ });
455
+ test("allows Task operations", async () => {
456
+ const result = await manager.checkPermission("Task", SAMPLE_INPUTS.task);
457
+ expect(result.decision).toBe("allow");
458
+ });
459
+ test("denies Write operations", async () => {
460
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
461
+ expect(result.decision).toBe("deny");
462
+ expect(result.reason).toContain("Plan mode");
463
+ });
464
+ test("denies Edit operations", async () => {
465
+ const result = await manager.checkPermission("Edit", SAMPLE_INPUTS.editFile);
466
+ expect(result.decision).toBe("deny");
467
+ expect(result.reason).toContain("write operations disabled");
468
+ });
469
+ test("denies Bash operations (even safe ones)", async () => {
470
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
471
+ expect(result.decision).toBe("deny");
472
+ });
473
+ test("denies NotebookEdit operations", async () => {
474
+ const result = await manager.checkPermission("NotebookEdit", { cell_id: "1" });
475
+ expect(result.decision).toBe("deny");
476
+ });
477
+ });
478
+ describe("default mode (interactive)", () => {
479
+ test("prompts for Write operations", async () => {
480
+ const mockPrompt = createMockPrompt("allow");
481
+ const manager = new PermissionManager("default", mockPrompt);
482
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
483
+ expect(result.decision).toBe("allow");
484
+ });
485
+ test("prompts for Bash commands", async () => {
486
+ const mockPrompt = createMockPrompt("deny");
487
+ const manager = new PermissionManager("default", mockPrompt);
488
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
489
+ expect(result.decision).toBe("deny");
490
+ });
491
+ test("caches allowAlways decision", async () => {
492
+ const mockPrompt = createMockPrompt("allowAlways");
493
+ const manager = new PermissionManager("default", mockPrompt);
494
+ // First call should trigger prompt
495
+ const result1 = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
496
+ expect(result1.decision).toBe("allowAlways");
497
+ // Second call should use cache
498
+ const result2 = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
499
+ expect(result2.decision).toBe("allow");
500
+ expect(result2.reason).toContain("Previously approved");
501
+ });
502
+ test("caches denyAlways decision", async () => {
503
+ const mockPrompt = createMockPrompt("denyAlways");
504
+ const manager = new PermissionManager("default", mockPrompt);
505
+ // First call should trigger prompt
506
+ const result1 = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
507
+ expect(result1.decision).toBe("denyAlways");
508
+ // Second call should use cache
509
+ const result2 = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
510
+ expect(result2.decision).toBe("deny");
511
+ expect(result2.reason).toContain("Previously denied");
512
+ });
513
+ });
514
+ describe("interactive mode", () => {
515
+ test("behaves same as default mode for prompting", async () => {
516
+ const mockPrompt = createMockPrompt("allow");
517
+ const manager = new PermissionManager("interactive", mockPrompt);
518
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
519
+ expect(result.decision).toBe("allow");
520
+ });
521
+ test("prompts for all operations", async () => {
522
+ const callOrder = [];
523
+ const trackingPrompt = async (req) => {
524
+ callOrder.push(req.toolName);
525
+ return { decision: "allow" };
526
+ };
527
+ const manager = new PermissionManager("interactive", trackingPrompt);
528
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
529
+ await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
530
+ await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
531
+ expect(callOrder).toEqual(["Read", "Write", "Bash"]);
532
+ });
533
+ });
534
+ });
535
+ // ============================================
536
+ // PERMISSION CACHING TESTS
537
+ // ============================================
538
+ describe("Permission Caching", () => {
539
+ let manager;
540
+ let promptCallCount;
541
+ let trackingPrompt;
542
+ beforeEach(() => {
543
+ promptCallCount = 0;
544
+ trackingPrompt = async (req) => {
545
+ promptCallCount++;
546
+ return { decision: "allowAlways" };
547
+ };
548
+ manager = new PermissionManager("default", trackingPrompt);
549
+ });
550
+ test("cache prevents repeated prompts for same file", async () => {
551
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
552
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
553
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
554
+ expect(promptCallCount).toBe(1);
555
+ });
556
+ test("different files trigger different prompts", async () => {
557
+ await manager.checkPermission("Read", { file_path: "/file1.ts" });
558
+ await manager.checkPermission("Read", { file_path: "/file2.ts" });
559
+ expect(promptCallCount).toBe(2);
560
+ });
561
+ test("cache key includes tool name", async () => {
562
+ await manager.checkPermission("Read", { file_path: "/file.ts" });
563
+ await manager.checkPermission("Write", { file_path: "/file.ts", content: "" });
564
+ expect(promptCallCount).toBe(2);
565
+ });
566
+ test("Bash cache key is based on command", async () => {
567
+ await manager.checkPermission("Bash", { command: "ls -la" });
568
+ await manager.checkPermission("Bash", { command: "ls -la" });
569
+ await manager.checkPermission("Bash", { command: "pwd" });
570
+ expect(promptCallCount).toBe(2);
571
+ });
572
+ test("clearCache removes all cached decisions", async () => {
573
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
574
+ expect(promptCallCount).toBe(1);
575
+ manager.clearCache();
576
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
577
+ expect(promptCallCount).toBe(2);
578
+ });
579
+ test("setMode clears cache", async () => {
580
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
581
+ expect(promptCallCount).toBe(1);
582
+ manager.setMode("default");
583
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
584
+ expect(promptCallCount).toBe(2);
585
+ });
586
+ test("cache timeout expires old decisions", async () => {
587
+ // This test verifies the cache timeout mechanism
588
+ // We create a manager with a very short timeout
589
+ const shortTimeoutManager = new PermissionManager("default", trackingPrompt);
590
+ // Access private cacheTimeout for testing (via any)
591
+ shortTimeoutManager.cacheTimeout = 10; // 10ms
592
+ await shortTimeoutManager.checkPermission("Read", SAMPLE_INPUTS.readFile);
593
+ expect(promptCallCount).toBe(1);
594
+ // Wait for cache to expire
595
+ await new Promise((resolve) => setTimeout(resolve, 50));
596
+ await shortTimeoutManager.checkPermission("Read", SAMPLE_INPUTS.readFile);
597
+ expect(promptCallCount).toBe(2);
598
+ });
599
+ test("allowAlways is cached, allow is not", async () => {
600
+ let decision = "allow";
601
+ const variablePrompt = async () => ({
602
+ decision,
603
+ });
604
+ manager = new PermissionManager("default", variablePrompt);
605
+ // First call with "allow"
606
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
607
+ decision = "deny";
608
+ // "allow" is not cached, so it prompts again
609
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
610
+ expect(result.decision).toBe("deny");
611
+ });
612
+ });
613
+ // ============================================
614
+ // PERMISSION PROMPT CALLBACK TESTS
615
+ // ============================================
616
+ describe("Permission Prompt Callbacks", () => {
617
+ test("custom callback receives correct request structure", async () => {
618
+ let receivedRequest = null;
619
+ const inspectingPrompt = async (req) => {
620
+ receivedRequest = req;
621
+ return { decision: "allow" };
622
+ };
623
+ const manager = new PermissionManager("default", inspectingPrompt);
624
+ await manager.checkPermission("Bash", { command: "echo test" });
625
+ expect(receivedRequest).not.toBeNull();
626
+ expect(receivedRequest.toolName).toBe("Bash");
627
+ expect(receivedRequest.toolInput).toEqual({ command: "echo test" });
628
+ expect(receivedRequest.riskLevel).toBeDefined();
629
+ expect(receivedRequest.description).toContain("Execute:");
630
+ expect(receivedRequest.command).toBe("echo test");
631
+ });
632
+ test("callback receives file path in request", async () => {
633
+ let receivedRequest = null;
634
+ const inspectingPrompt = async (req) => {
635
+ receivedRequest = req;
636
+ return { decision: "allow" };
637
+ };
638
+ const manager = new PermissionManager("default", inspectingPrompt);
639
+ await manager.checkPermission("Write", { file_path: "/test.ts", content: "x" });
640
+ expect(receivedRequest.file).toBe("/test.ts");
641
+ });
642
+ test("callback can return allowAlways", async () => {
643
+ const manager = new PermissionManager("default", createMockPrompt("allowAlways"));
644
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
645
+ expect(result.decision).toBe("allowAlways");
646
+ });
647
+ test("callback can return denyAlways", async () => {
648
+ const manager = new PermissionManager("default", createMockPrompt("denyAlways"));
649
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
650
+ expect(result.decision).toBe("denyAlways");
651
+ });
652
+ test("async callbacks work correctly", async () => {
653
+ const manager = new PermissionManager("default", createDelayedMockPrompt("allow", 50));
654
+ const start = Date.now();
655
+ const result = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
656
+ const elapsed = Date.now() - start;
657
+ expect(result.decision).toBe("allow");
658
+ expect(elapsed).toBeGreaterThanOrEqual(40); // Allow some timing variance
659
+ });
660
+ test("callback reason is preserved in result", async () => {
661
+ const reasonPrompt = async () => ({
662
+ decision: "deny",
663
+ reason: "Custom denial reason",
664
+ });
665
+ const manager = new PermissionManager("default", reasonPrompt);
666
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
667
+ expect(result.decision).toBe("deny");
668
+ expect(result.reason).toBe("Custom denial reason");
669
+ });
670
+ });
671
+ // ============================================
672
+ // SECURITY-CRITICAL PATHS TESTS
673
+ // ============================================
674
+ describe("Security-Critical Paths", () => {
675
+ describe("sensitive file protection", () => {
676
+ test("Write to .env requires prompt in default mode", async () => {
677
+ const mockPrompt = createMockPrompt("deny");
678
+ const manager = new PermissionManager("default", mockPrompt);
679
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.envFile);
680
+ expect(result.decision).toBe("deny");
681
+ });
682
+ test("Write to .ssh directory requires prompt in acceptEdits mode", async () => {
683
+ const mockPrompt = createMockPrompt("deny");
684
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
685
+ // acceptEdits allows Write, but we want to verify sensitive files are flagged
686
+ // Note: Current implementation allows this in acceptEdits - this test documents behavior
687
+ const result = await manager.checkPermission("Write", SAMPLE_INPUTS.sshKey);
688
+ // In acceptEdits, file writes are auto-allowed regardless of risk
689
+ expect(result.decision).toBe("allow");
690
+ });
691
+ });
692
+ describe("destructive command protection", () => {
693
+ test("SECURITY GAP: rm -rf does NOT require prompt in acceptEdits mode", async () => {
694
+ // BUG DOCUMENTATION: Critical commands fall through to default allow
695
+ const mockPrompt = createMockPrompt("deny");
696
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
697
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashCritical);
698
+ // CURRENT BEHAVIOR: allow (falls through) - should be deny or prompt
699
+ expect(result.decision).toBe("allow");
700
+ });
701
+ test("SECURITY GAP: git push --force does NOT require prompt in acceptEdits mode", async () => {
702
+ // BUG DOCUMENTATION: Critical commands fall through to default allow
703
+ const mockPrompt = createMockPrompt("deny");
704
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
705
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashForcePush);
706
+ // CURRENT BEHAVIOR: allow (falls through) - should be deny or prompt
707
+ expect(result.decision).toBe("allow");
708
+ });
709
+ test("SECURITY GAP: git reset --hard does NOT require prompt in acceptEdits mode", async () => {
710
+ // BUG DOCUMENTATION: Critical commands fall through to default allow
711
+ const mockPrompt = createMockPrompt("deny");
712
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
713
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashResetHard);
714
+ // CURRENT BEHAVIOR: allow (falls through) - should be deny or prompt
715
+ expect(result.decision).toBe("allow");
716
+ });
717
+ });
718
+ describe("privilege escalation protection", () => {
719
+ test("SECURITY GAP: sudo commands do NOT require prompt in acceptEdits mode", async () => {
720
+ // BUG DOCUMENTATION: High risk commands fall through to default allow
721
+ const mockPrompt = createMockPrompt("deny");
722
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
723
+ const result = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSudo);
724
+ // CURRENT BEHAVIOR: allow (falls through) - should be deny or prompt
725
+ expect(result.decision).toBe("allow");
726
+ });
727
+ test("chmod commands are correctly identified as high risk", () => {
728
+ const riskLevel = assessRiskLevel("Bash", { command: "chmod 777 file" });
729
+ expect(riskLevel).toBe("high");
730
+ });
731
+ });
732
+ describe("mode transition security", () => {
733
+ test("switching to bypassPermissions clears cache", async () => {
734
+ const manager = new PermissionManager("default", createMockPrompt("allowAlways"));
735
+ // Cache a decision
736
+ await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
737
+ // Switch mode
738
+ manager.setMode("bypassPermissions");
739
+ // Cache should be cleared - verify by checking internal state
740
+ const cache = manager.cache;
741
+ expect(Object.keys(cache).length).toBe(0);
742
+ });
743
+ test("switching from bypassPermissions to default re-enables prompts", async () => {
744
+ let promptCalled = false;
745
+ const trackingPrompt = async () => {
746
+ promptCalled = true;
747
+ return { decision: "allow" };
748
+ };
749
+ const manager = new PermissionManager("bypassPermissions", trackingPrompt);
750
+ // No prompt in bypass mode
751
+ await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
752
+ expect(promptCalled).toBe(false);
753
+ // Switch to default
754
+ manager.setMode("default");
755
+ // Prompt should be called now
756
+ await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
757
+ expect(promptCalled).toBe(true);
758
+ });
759
+ });
760
+ describe("edge case security", () => {
761
+ test("empty command in Bash doesn't bypass security", async () => {
762
+ const riskLevel = assessRiskLevel("Bash", { command: "" });
763
+ expect(riskLevel).toBe("medium");
764
+ });
765
+ test("null file path doesn't cause error", async () => {
766
+ const manager = new PermissionManager("default", createMockPrompt("allow"));
767
+ const result = await manager.checkPermission("Write", { file_path: null, content: "x" });
768
+ expect(result.decision).toBe("allow");
769
+ });
770
+ test("undefined input doesn't bypass security", async () => {
771
+ const riskLevel = assessRiskLevel("Bash", { command: undefined });
772
+ expect(riskLevel).toBe("medium");
773
+ });
774
+ test("very long commands are handled safely", async () => {
775
+ const veryLongCommand = "echo " + "a".repeat(10000);
776
+ const desc = generateDescription("Bash", { command: veryLongCommand });
777
+ expect(desc.length).toBeLessThan(200);
778
+ expect(desc).toContain("...");
779
+ });
780
+ });
781
+ });
782
+ // ============================================
783
+ // CONCURRENCY TESTS
784
+ // ============================================
785
+ describe("Concurrency", () => {
786
+ test("handles concurrent permission checks", async () => {
787
+ const manager = new PermissionManager("default", createMockPrompt("allow"));
788
+ const checks = await Promise.all([
789
+ manager.checkPermission("Read", { file_path: "/file1.ts" }),
790
+ manager.checkPermission("Read", { file_path: "/file2.ts" }),
791
+ manager.checkPermission("Read", { file_path: "/file3.ts" }),
792
+ ]);
793
+ expect(checks.every((r) => r.decision === "allow")).toBe(true);
794
+ });
795
+ test("cache is thread-safe for concurrent access", async () => {
796
+ let promptCount = 0;
797
+ const countingPrompt = async () => {
798
+ promptCount++;
799
+ await new Promise((r) => setTimeout(r, 10));
800
+ return { decision: "allowAlways" };
801
+ };
802
+ const manager = new PermissionManager("default", countingPrompt);
803
+ // All these should trigger prompts (no caching yet)
804
+ await Promise.all([
805
+ manager.checkPermission("Read", { file_path: "/same.ts" }),
806
+ manager.checkPermission("Read", { file_path: "/same.ts" }),
807
+ manager.checkPermission("Read", { file_path: "/same.ts" }),
808
+ ]);
809
+ // Without proper locking, all three might prompt
810
+ // With caching, only the first should prompt
811
+ // Note: This test documents current behavior - actual thread safety
812
+ // depends on JavaScript's single-threaded nature
813
+ expect(promptCount).toBeGreaterThanOrEqual(1);
814
+ });
815
+ });
816
+ // ============================================
817
+ // INTEGRATION TESTS
818
+ // ============================================
819
+ describe("Integration Tests", () => {
820
+ test("full workflow: read, edit, write in acceptEdits mode", async () => {
821
+ const manager = new PermissionManager("acceptEdits");
822
+ const readResult = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
823
+ expect(readResult.decision).toBe("allow");
824
+ const editResult = await manager.checkPermission("Edit", SAMPLE_INPUTS.editFile);
825
+ expect(editResult.decision).toBe("allow");
826
+ const writeResult = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
827
+ expect(writeResult.decision).toBe("allow");
828
+ });
829
+ test("full workflow: plan mode allows read-only only", async () => {
830
+ const manager = new PermissionManager("plan");
831
+ const readResult = await manager.checkPermission("Read", SAMPLE_INPUTS.readFile);
832
+ expect(readResult.decision).toBe("allow");
833
+ const globResult = await manager.checkPermission("Glob", SAMPLE_INPUTS.glob);
834
+ expect(globResult.decision).toBe("allow");
835
+ const grepResult = await manager.checkPermission("Grep", SAMPLE_INPUTS.grep);
836
+ expect(grepResult.decision).toBe("allow");
837
+ const writeResult = await manager.checkPermission("Write", SAMPLE_INPUTS.writeFile);
838
+ expect(writeResult.decision).toBe("deny");
839
+ });
840
+ test("SECURITY GAP: full workflow - critical commands do NOT require prompt in acceptEdits", async () => {
841
+ const mockPrompt = createMockPrompt("deny");
842
+ const manager = new PermissionManager("acceptEdits", mockPrompt);
843
+ // Safe commands allowed
844
+ const safeResult = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashSafe);
845
+ expect(safeResult.decision).toBe("allow");
846
+ // BUG: Critical commands should require prompt but fall through to allow
847
+ const criticalResult = await manager.checkPermission("Bash", SAMPLE_INPUTS.bashCritical);
848
+ // CURRENT BEHAVIOR: allow (falls through) - should be deny or prompt
849
+ expect(criticalResult.decision).toBe("allow");
850
+ });
851
+ });