clawmini 0.0.7 → 0.0.9

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 (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.Dr0ot9sV.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +42 -13
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -176
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.Dr0ot9sV.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  328. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  329. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  331. package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import path from 'node:path';
3
+ import fsPromises from 'node:fs/promises';
4
+ import { createTRPCClient, httpLink, TRPCClientError } from '@trpc/client';
5
+ import { TestEnvironment, type ChatSubscription, policyWith } from '../_helpers/test-environment.js';
6
+ import type { AgentRouter } from '../../src/daemon/api/agent-router.js';
7
+
8
+ describe('Context-Aware Execution E2E', () => {
9
+ let env: TestEnvironment;
10
+ let chat: ChatSubscription | undefined;
11
+
12
+ beforeAll(async () => {
13
+ env = new TestEnvironment('e2e-context-cwd');
14
+ await env.setup();
15
+ await env.setupSubagentEnv({
16
+ policies: {
17
+ 'print-cwd': {
18
+ description: 'Print the current working directory',
19
+ command: 'pwd',
20
+ args: [],
21
+ autoApprove: true,
22
+ },
23
+ },
24
+ });
25
+
26
+ // Create a 'foo' subdirectory inside the debug-agent's working directory.
27
+ await fsPromises.mkdir(path.join(env.e2eDir, 'debug-agent', 'foo'), { recursive: true });
28
+ }, 30000);
29
+
30
+ afterAll(() => env.teardown(), 30000);
31
+ afterEach(() => env.disconnectAll());
32
+
33
+ it('should execute policy in the requested subdirectory', async () => {
34
+ await env.addChat('chat-cwd');
35
+ chat = await env.connect('chat-cwd');
36
+
37
+ // Simulate the agent navigating to 'foo' and calling the policy.
38
+ await env.sendMessage('cd foo && clawmini-lite.js request print-cwd', {
39
+ chat: 'chat-cwd',
40
+ agent: 'debug-agent',
41
+ });
42
+
43
+ const policy = await chat.waitForMessage(policyWith('approved'));
44
+ expect(policy.content).toContain(path.join('debug-agent', 'foo'));
45
+ }, 30000);
46
+
47
+ // The tests below send a *crafted* sandbox-relative cwd directly to the
48
+ // tRPC endpoint, because lite always sends process.cwd() (an absolute host
49
+ // path). We extract the debug-agent's API credentials by asking it to echo
50
+ // them to stdout.
51
+ describe('direct tRPC cwd handling', () => {
52
+ let agentClient: ReturnType<typeof createTRPCClient<AgentRouter>>;
53
+
54
+ beforeAll(async () => {
55
+ const { url, token } = await env.getAgentCredentials();
56
+ agentClient = createTRPCClient<AgentRouter>({
57
+ links: [httpLink({ url, headers: () => ({ Authorization: `Bearer ${token}` }) })],
58
+ });
59
+ }, 30000);
60
+
61
+ it('should strip environment baseDir from a sandbox-relative cwd', async () => {
62
+ // Configure an environment with a baseDir. The environment matches the
63
+ // debug-agent directory, so when the agent reports cwd `/sandbox/foo`,
64
+ // the server strips `/sandbox` and executes inside `<agentDir>/foo`.
65
+ const envConfigDir = path.resolve(env.e2eDir, '.clawmini/environments/sandboxed');
66
+ await fsPromises.mkdir(envConfigDir, { recursive: true });
67
+ await fsPromises.writeFile(
68
+ path.join(envConfigDir, 'env.json'),
69
+ JSON.stringify({ baseDir: '/sandbox' })
70
+ );
71
+
72
+ env.updateSettings({ environments: { './debug-agent': 'sandboxed' } });
73
+
74
+ const result = await agentClient.createPolicyRequest.mutate({
75
+ commandName: 'print-cwd',
76
+ args: [],
77
+ fileMappings: {},
78
+ cwd: '/sandbox/foo',
79
+ });
80
+
81
+ expect(result.executionResult).toBeDefined();
82
+ expect(result.executionResult!.exitCode).toBe(0);
83
+ expect(result.executionResult!.stdout).toContain(path.join('debug-agent', 'foo'));
84
+ }, 15000);
85
+
86
+ it('should translate sandbox cwd when the env targets a parent of the agent dir', async () => {
87
+ // Simulates a cladding-like setup: the environment is enabled at the
88
+ // workspace root, and the agent lives in a subdirectory inside it. The
89
+ // agent (running in the VM) reports its cwd relative to the sandbox
90
+ // baseDir (`/vm-workspace/debug-agent/nested`), so the server must
91
+ // strip baseDir and resolve the remainder against the env target path
92
+ // (the workspace root) — not the agent dir, which would double-nest.
93
+ const envConfigDir = path.resolve(env.e2eDir, '.clawmini/environments/cladding-like');
94
+ await fsPromises.mkdir(envConfigDir, { recursive: true });
95
+ await fsPromises.writeFile(
96
+ path.join(envConfigDir, 'env.json'),
97
+ JSON.stringify({ baseDir: '/vm-workspace' })
98
+ );
99
+
100
+ const settings = env.getSettings();
101
+ settings.environments = { './': 'cladding-like' };
102
+ env.writeSettings(settings);
103
+
104
+ await fsPromises.mkdir(path.join(env.e2eDir, 'debug-agent', 'nested'), { recursive: true });
105
+
106
+ const result = await agentClient.createPolicyRequest.mutate({
107
+ commandName: 'print-cwd',
108
+ args: [],
109
+ fileMappings: {},
110
+ cwd: '/vm-workspace/debug-agent/nested',
111
+ });
112
+
113
+ expect(result.executionResult).toBeDefined();
114
+ expect(result.executionResult!.exitCode).toBe(0);
115
+ expect(result.executionResult!.stdout.trim()).toBe(
116
+ path.join(env.e2eDir, 'debug-agent', 'nested')
117
+ );
118
+ }, 15000);
119
+
120
+ it('should reject a cwd that escapes the env target dir', async () => {
121
+ // With the cladding-like env from the previous test still active, a
122
+ // sandbox cwd that resolves above the env target must be rejected.
123
+ await expect(
124
+ agentClient.createPolicyRequest.mutate({
125
+ commandName: 'print-cwd',
126
+ args: [],
127
+ fileMappings: {},
128
+ cwd: '/vm-workspace/../etc/passwd',
129
+ })
130
+ ).rejects.toSatisfy((err: unknown) => {
131
+ return (
132
+ err instanceof TRPCClientError &&
133
+ /Security Error: Path resolves outside/.test(err.message)
134
+ );
135
+ });
136
+ }, 15000);
137
+
138
+ it('should reject a cwd that escapes the agent directory', async () => {
139
+ // Remove the environment setup from the previous test so the baseDir
140
+ // branch does not apply here.
141
+ const settings = env.getSettings();
142
+ delete settings.environments;
143
+ env.writeSettings(settings);
144
+
145
+ await expect(
146
+ agentClient.createPolicyRequest.mutate({
147
+ commandName: 'print-cwd',
148
+ args: [],
149
+ fileMappings: {},
150
+ cwd: '../../escape',
151
+ })
152
+ ).rejects.toSatisfy((err: unknown) => {
153
+ return (
154
+ err instanceof TRPCClientError &&
155
+ /Security Error: Path resolves outside/.test(err.message)
156
+ );
157
+ });
158
+ }, 15000);
159
+ });
160
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { TestEnvironment } from '../_helpers/test-environment.js';
5
+
6
+ // Regression test: policies whose `command` is a relative path (e.g. the
7
+ // built-in run-host uses `./.clawmini/policy-scripts/run-host.js`) must run
8
+ // successfully even when the triggering agent's cwd is somewhere other than
9
+ // the workspace root. resolvePolicies now resolves relative paths against the
10
+ // workspace root so spawn no longer depends on the caller's cwd.
11
+ describe('Policy with relative script path', () => {
12
+ let env: TestEnvironment;
13
+
14
+ beforeAll(async () => {
15
+ env = new TestEnvironment('e2e-policy-relative-path');
16
+ await env.setup();
17
+ await env.setupSubagentEnv();
18
+
19
+ const scriptRelDir = 'tools';
20
+ const scriptRelPath = `./${scriptRelDir}/relative-policy.sh`;
21
+ const scriptAbsDir = path.resolve(env.e2eDir, scriptRelDir);
22
+ const scriptAbsPath = path.resolve(env.e2eDir, scriptRelPath);
23
+
24
+ fs.mkdirSync(scriptAbsDir, { recursive: true });
25
+ fs.writeFileSync(
26
+ scriptAbsPath,
27
+ '#!/bin/sh\necho "arg: $1"\n',
28
+ { mode: 0o755 }
29
+ );
30
+
31
+ // autoApprove:true executes the policy directly from
32
+ // agent-policy-endpoints.createPolicyRequest, where hostCwd is resolved
33
+ // from the requesting agent's directory — making this path diverge from
34
+ // the workspace root even for the default configuration.
35
+ env.writePolicies({
36
+ 'relative-policy': {
37
+ description: 'Policy whose command is a relative path from the workspace root',
38
+ command: scriptRelPath,
39
+ autoApprove: true,
40
+ },
41
+ });
42
+ }, 30000);
43
+
44
+ afterAll(() => env.teardown(), 30000);
45
+
46
+ it('runs the script regardless of the requesting agent cwd', async () => {
47
+ // The debug-agent runs commands in its own work dir (<workspaceRoot>/debug-agent),
48
+ // so a relative `command` of `./tools/relative-policy.sh` would resolve
49
+ // against that subdirectory without the fix — which does not contain the
50
+ // script. runLite proxies through the debug-agent's CLAW_API_URL/TOKEN so
51
+ // the request is created with that agent's identity.
52
+ const { stdout, code } = await env.runLite(
53
+ ['request', 'relative-policy', '--', 'hello'],
54
+ { cwd: path.resolve(env.e2eDir, 'debug-agent') }
55
+ );
56
+
57
+ expect(code).toBe(0);
58
+ expect(stdout).toContain('arg: hello');
59
+ }, 30000);
60
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { TestEnvironment } from '../_helpers/test-environment.js';
5
+
6
+ describe('E2E `requests show` script size handling', () => {
7
+ let env: TestEnvironment;
8
+ let agentDir = '';
9
+
10
+ beforeAll(async () => {
11
+ env = new TestEnvironment('e2e-tmp-requests-show');
12
+ await env.setup();
13
+ await env.setupSubagentEnv({
14
+ policies: {
15
+ 'small-script': {
16
+ description: 'A small inline-able script',
17
+ command: './.clawmini/policy-scripts/small-script.sh',
18
+ },
19
+ 'large-script': {
20
+ description: 'A large script that should spill to tmp',
21
+ command: './.clawmini/policy-scripts/large-script.sh',
22
+ },
23
+ 'system-cmd': {
24
+ description: 'A system command (no script body)',
25
+ command: 'echo',
26
+ args: ['hi'],
27
+ },
28
+ 'symlink-escape': {
29
+ description: 'A symlink in policy-scripts/ pointing outside it',
30
+ command: './.clawmini/policy-scripts/symlink-escape.sh',
31
+ },
32
+ },
33
+ });
34
+
35
+ const scriptsDir = env.getClawminiPath('policy-scripts');
36
+ fs.mkdirSync(scriptsDir, { recursive: true });
37
+ fs.writeFileSync(
38
+ path.join(scriptsDir, 'small-script.sh'),
39
+ '#!/bin/sh\necho hello-from-small\n',
40
+ { mode: 0o755 }
41
+ );
42
+ // 5000 chars > MAX_INLINE_SCRIPT_LENGTH (4000), forcing the spill path.
43
+ fs.writeFileSync(
44
+ path.join(scriptsDir, 'large-script.sh'),
45
+ '#!/bin/sh\n' + 'x'.repeat(5000) + '\n',
46
+ { mode: 0o755 }
47
+ );
48
+
49
+ // Plant a sensitive file outside policy-scripts/, then a symlink inside
50
+ // policy-scripts/ pointing at it. The endpoint must refuse to read the
51
+ // symlink target — string-prefix containment alone would allow this.
52
+ const sensitive = path.resolve(env.e2eDir, 'outside-sensitive.txt');
53
+ fs.writeFileSync(sensitive, 'top secret\n');
54
+ fs.symlinkSync(sensitive, path.join(scriptsDir, 'symlink-escape.sh'));
55
+
56
+ agentDir = path.resolve(env.e2eDir, 'debug-agent');
57
+ await env.getAgentCredentials();
58
+ }, 30000);
59
+
60
+ afterAll(() => env.teardown(), 30000);
61
+
62
+ const runLite = (args: string[]) => env.runLite(args, { cwd: agentDir });
63
+
64
+ it('shows a small script body inline', async () => {
65
+ const { stdout, stderr, code } = await runLite(['requests', 'show', 'small-script']);
66
+
67
+ expect(stderr).toBe('');
68
+ expect(code).toBe(0);
69
+ expect(stdout).toContain('"description": "A small inline-able script"');
70
+ expect(stdout).toContain('--- Script:');
71
+ expect(stdout).toContain('hello-from-small');
72
+ expect(stdout).not.toContain('copied to ./tmp/');
73
+ });
74
+
75
+ it('spills a large script to the agent tmp dir instead of inlining', async () => {
76
+ const { stdout, stderr, code } = await runLite(['requests', 'show', 'large-script']);
77
+
78
+ expect(stderr).toBe('');
79
+ expect(code).toBe(0);
80
+ expect(stdout).toContain('--- Script:');
81
+ expect(stdout).toContain('copied to ./tmp/policy-script-large-script.sh');
82
+ // The huge body must not be inlined.
83
+ expect(stdout).not.toContain('x'.repeat(100));
84
+
85
+ const spillPath = path.join(agentDir, 'tmp', 'policy-script-large-script.sh');
86
+ expect(fs.existsSync(spillPath)).toBe(true);
87
+ const spilled = fs.readFileSync(spillPath, 'utf8');
88
+ expect(spilled).toContain('x'.repeat(5000));
89
+ expect(spilled.startsWith('#!/bin/sh')).toBe(true);
90
+ });
91
+
92
+ it('overwrites the same spill file when shown again', async () => {
93
+ const spillPath = path.join(agentDir, 'tmp', 'policy-script-large-script.sh');
94
+ const before = fs.statSync(spillPath).size;
95
+
96
+ // Update the source script to a different (still-large) body.
97
+ const scriptPath = env.getClawminiPath('policy-scripts', 'large-script.sh');
98
+ fs.writeFileSync(scriptPath, '#!/bin/sh\n' + 'y'.repeat(6000) + '\n', { mode: 0o755 });
99
+
100
+ const { code } = await runLite(['requests', 'show', 'large-script']);
101
+ expect(code).toBe(0);
102
+
103
+ const after = fs.readFileSync(spillPath, 'utf8');
104
+ expect(after).toContain('y'.repeat(6000));
105
+ expect(after).not.toContain('x'.repeat(100));
106
+ expect(fs.statSync(spillPath).size).not.toBe(before);
107
+ });
108
+
109
+ it('prints JSON only with no script body for system-command policies', async () => {
110
+ const { stdout, stderr, code } = await runLite(['requests', 'show', 'system-cmd']);
111
+
112
+ expect(stderr).toBe('');
113
+ expect(code).toBe(0);
114
+ expect(stdout).toContain('"command": "echo"');
115
+ expect(stdout).toContain('(no script body:');
116
+ expect(stdout).not.toContain('--- Script:');
117
+ });
118
+
119
+ it('refuses to read a symlink in policy-scripts/ that points outside', async () => {
120
+ const { stdout, stderr, code } = await runLite([
121
+ 'requests',
122
+ 'show',
123
+ 'symlink-escape',
124
+ ]);
125
+
126
+ expect(stderr).toBe('');
127
+ expect(code).toBe(0);
128
+ // The body itself must never appear, regardless of what error path the
129
+ // endpoint takes.
130
+ expect(stdout).not.toContain('top secret');
131
+ // The CLI surfaces BAD_REQUEST as a "(no script body: ...)" hint.
132
+ expect(stdout).toContain('(no script body:');
133
+ expect(stdout).toContain('does not point at a script in policy-scripts/');
134
+ });
135
+ });
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import fsPromises from 'node:fs/promises';
5
+ import { TestEnvironment } from '../_helpers/test-environment.js';
6
+
7
+ describe('E2E Requests Tests (Lite)', () => {
8
+ let env: TestEnvironment;
9
+ let agentDir = '';
10
+
11
+ beforeAll(async () => {
12
+ env = new TestEnvironment('e2e-tmp-requests');
13
+ await env.setup();
14
+ await env.setupSubagentEnv({
15
+ policies: {
16
+ 'test-cmd': {
17
+ description: 'A test policy',
18
+ command: 'echo',
19
+ args: ['hello'],
20
+ allowHelp: true,
21
+ },
22
+ 'no-help-cmd': {
23
+ description: 'A no help policy',
24
+ command: 'echo',
25
+ args: ['nohelp'],
26
+ },
27
+ 'auto-cmd': {
28
+ description: 'An auto approve policy',
29
+ command: 'echo',
30
+ args: ['autoresult'],
31
+ autoApprove: true,
32
+ },
33
+ 'interp-cmd': {
34
+ description: 'Auto-approve cat with {{myfile}} interpolation',
35
+ command: 'cat',
36
+ args: ['{{myfile}}'],
37
+ autoApprove: true,
38
+ },
39
+ },
40
+ });
41
+
42
+ agentDir = path.resolve(env.e2eDir, 'debug-agent');
43
+ await env.getAgentCredentials();
44
+ }, 30000);
45
+
46
+ afterAll(() => env.teardown(), 30000);
47
+
48
+ // All requests tests spawn lite from the debug-agent's working dir so
49
+ // --file relative paths resolve correctly.
50
+ const runLite = (args: string[]) => env.runLite(args, { cwd: agentDir });
51
+
52
+ it('should list policies', async () => {
53
+ const { stdout, code } = await runLite(['requests', 'list']);
54
+ expect(code).toBe(0);
55
+ expect(stdout).toContain('Available Policies:');
56
+ expect(stdout).toContain('- test-cmd');
57
+ expect(stdout).toContain('Description: A test policy');
58
+ });
59
+
60
+ it('should run --help on underlying command', async () => {
61
+ const { stdout, code } = await runLite(['request', 'test-cmd', '--help']);
62
+ expect(code).toBe(0);
63
+ expect(stdout).toBeTruthy();
64
+ });
65
+
66
+ it('should block --help if allowHelp is not true', async () => {
67
+ const { stderr, code } = await runLite(['request', 'no-help-cmd', '--help']);
68
+ expect(code).toBe(1);
69
+ expect(stderr).toContain('This command does not support --help');
70
+ });
71
+
72
+ it('should create a request and return an ID', async () => {
73
+ const dummyFilePath = path.join(agentDir, 'dummy.txt');
74
+ await fsPromises.writeFile(dummyFilePath, 'dummy content');
75
+
76
+ const { stdout, stderr, code } = await runLite([
77
+ 'request',
78
+ 'test-cmd',
79
+ '--file',
80
+ `target=${dummyFilePath}`,
81
+ '--',
82
+ 'extra1',
83
+ 'extra2',
84
+ ]);
85
+
86
+ expect(stderr).toBe('');
87
+ expect(code).toBe(0);
88
+ expect(stdout).toContain('Request created successfully.');
89
+ expect(stdout).toContain('Request ID:');
90
+ // Guidance for the agent: explain the request is queued, how the result
91
+ // arrives, that polling is forbidden, and what to do next. Normalize
92
+ // whitespace so assertions don't depend on where console.log wraps.
93
+ const flat = stdout.replace(/\s+/g, ' ');
94
+ expect(flat).toContain('has not run yet');
95
+ expect(flat).toContain('queued for user approval');
96
+ expect(flat).toContain('new user message in this chat');
97
+ expect(flat).toContain('do not poll');
98
+ expect(flat).toContain('end your turn');
99
+
100
+ const requestsDir = path.join(env.e2eDir, '.clawmini', 'tmp', 'requests');
101
+ const files = (await fsPromises.readdir(requestsDir)).filter((f) => f.endsWith('.json'));
102
+ expect(files.length).toBe(1);
103
+
104
+ const req = JSON.parse(await fsPromises.readFile(path.join(requestsDir, files[0]!), 'utf8'));
105
+ expect(req.commandName).toBe('test-cmd');
106
+ expect(req.args).toEqual(['extra1', 'extra2']);
107
+ expect(req.fileMappings).toHaveProperty('target');
108
+
109
+ const snapshotContent = await fsPromises.readFile(req.fileMappings.target, 'utf8');
110
+ expect(snapshotContent).toBe('dummy content');
111
+ });
112
+
113
+ it('should synchronously output execution result for auto-approved policy', async () => {
114
+ const { stdout, stderr, code } = await runLite(['request', 'auto-cmd', '--', 'extra-auto']);
115
+
116
+ expect(stderr).toBe('');
117
+ expect(code).toBe(0);
118
+ expect(stdout.trim()).toBe('autoresult extra-auto');
119
+ // Guidance text is only for queued requests — auto-approved policies
120
+ // run synchronously and must not include it.
121
+ expect(stdout).not.toContain('has not run yet');
122
+ expect(stdout).not.toContain('do not poll');
123
+ });
124
+
125
+ it('should interpolate {{placeholder}} in policy args with snapshot path', async () => {
126
+ const sourcePath = path.join(agentDir, 'interp-source.txt');
127
+ await fsPromises.writeFile(sourcePath, 'interpolated file content');
128
+
129
+ const { stdout, stderr, code } = await runLite([
130
+ 'request',
131
+ 'interp-cmd',
132
+ '--file',
133
+ `myfile=interp-source.txt`,
134
+ ]);
135
+
136
+ expect(stderr).toBe('');
137
+ expect(code).toBe(0);
138
+ expect(stdout).toContain('interpolated file content');
139
+ });
140
+
141
+ describe('--file snapshot security', () => {
142
+ it('should reject a --file path that resolves outside the agent directory', async () => {
143
+ const { stderr, code } = await runLite([
144
+ 'request',
145
+ 'test-cmd',
146
+ '--file',
147
+ `target=../../../etc/hosts`,
148
+ ]);
149
+
150
+ expect(code).toBe(1);
151
+ expect(stderr).toMatch(/Security Error: Path resolves outside/);
152
+ });
153
+
154
+ it('should reject a --file pointing at an absolute path outside the agent directory', async () => {
155
+ const { stderr, code } = await runLite([
156
+ 'request',
157
+ 'test-cmd',
158
+ '--file',
159
+ `target=/etc/hosts`,
160
+ ]);
161
+
162
+ expect(code).toBe(1);
163
+ expect(stderr).toMatch(/Security Error: Path resolves outside/);
164
+ });
165
+
166
+ it('should reject a --file that is a symlink', async () => {
167
+ fs.symlinkSync('/etc/hosts', path.join(agentDir, 'link-to-host.txt'));
168
+
169
+ const { stderr, code } = await runLite([
170
+ 'request',
171
+ 'test-cmd',
172
+ '--file',
173
+ `target=link-to-host.txt`,
174
+ ]);
175
+
176
+ expect(code).toBe(1);
177
+ expect(stderr).toMatch(/Security Error: Symlinks are not allowed/);
178
+ });
179
+
180
+ it('should reject a --file larger than the 5MB snapshot cap', async () => {
181
+ await fsPromises.writeFile(path.join(agentDir, 'big.bin'), Buffer.alloc(6 * 1024 * 1024));
182
+
183
+ const { stderr, code } = await runLite([
184
+ 'request',
185
+ 'test-cmd',
186
+ '--file',
187
+ `target=big.bin`,
188
+ ]);
189
+
190
+ expect(code).toBe(1);
191
+ expect(stderr).toMatch(/exceeds maximum snapshot size of 5MB/);
192
+ });
193
+
194
+ it('should reject a --file pointing at a directory', async () => {
195
+ fs.mkdirSync(path.join(agentDir, 'a-directory'));
196
+
197
+ const { stderr, code } = await runLite([
198
+ 'request',
199
+ 'test-cmd',
200
+ '--file',
201
+ `target=a-directory`,
202
+ ]);
203
+
204
+ expect(code).toBe(1);
205
+ expect(stderr).toMatch(/Requested path is not a file/);
206
+ });
207
+ });
208
+ });