@probelabs/visor 0.1.127 → 0.1.129

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 (255) hide show
  1. package/README.md +31 -1
  2. package/defaults/.visor.yaml +420 -0
  3. package/dist/ai-review-service.d.ts +1 -0
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/cli-main.d.ts.map +1 -1
  6. package/dist/config.d.ts +4 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/daemon.js +19 -0
  9. package/dist/defaults/.visor.yaml +420 -0
  10. package/dist/docs/commands.md +1 -1
  11. package/dist/docs/debugging.md +133 -0
  12. package/dist/docs/dev-playbook.md +10 -0
  13. package/dist/docs/index.md +1 -0
  14. package/dist/docs/scheduler.md +503 -0
  15. package/dist/docs/slack-integration.md +21 -0
  16. package/dist/docs/timeouts.md +1 -1
  17. package/dist/docs/workflow-creation-guide.md +39 -0
  18. package/dist/examples/README.md +30 -0
  19. package/dist/examples/calculator-config.yaml +4 -4
  20. package/dist/examples/sandbox-basic.yaml +18 -0
  21. package/dist/examples/sandbox-cache.yaml +32 -0
  22. package/dist/examples/sandbox-dockerfile-inline.yaml +22 -0
  23. package/dist/examples/sandbox-env-passthrough.yaml +26 -0
  24. package/dist/examples/sandbox-multi-env.yaml +27 -0
  25. package/dist/examples/sandbox-read-only.yaml +33 -0
  26. package/dist/examples/scheduler-example.yaml +118 -0
  27. package/dist/frontends/host.d.ts.map +1 -1
  28. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  29. package/dist/generated/config-schema.d.ts +230 -9
  30. package/dist/generated/config-schema.d.ts.map +1 -1
  31. package/dist/index.js +13676 -1604
  32. package/dist/mcp-server.d.ts +8 -8
  33. package/dist/{traces/run-2026-02-01T09-59-08-165Z.ndjson → output/traces/run-2026-02-08T18-16-04-160Z.ndjson} +84 -84
  34. package/dist/{traces/run-2026-02-01T09-59-52-595Z.ndjson → output/traces/run-2026-02-08T18-16-51-253Z.ndjson} +1029 -1029
  35. package/dist/providers/ai-check-provider.d.ts +16 -0
  36. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  37. package/dist/providers/check-provider.interface.d.ts +5 -0
  38. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  39. package/dist/providers/command-check-provider.d.ts.map +1 -1
  40. package/dist/providers/log-check-provider.d.ts.map +1 -1
  41. package/dist/providers/mcp-check-provider.d.ts +3 -0
  42. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  43. package/dist/providers/mcp-custom-sse-server.d.ts +22 -2
  44. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  45. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  46. package/dist/providers/workflow-tool-executor.d.ts +2 -0
  47. package/dist/providers/workflow-tool-executor.d.ts.map +1 -1
  48. package/dist/sandbox/cache-volume-manager.d.ts +48 -0
  49. package/dist/sandbox/cache-volume-manager.d.ts.map +1 -0
  50. package/dist/sandbox/check-runner.d.ts +25 -0
  51. package/dist/sandbox/check-runner.d.ts.map +1 -0
  52. package/dist/sandbox/docker-compose-sandbox.d.ts +25 -0
  53. package/dist/sandbox/docker-compose-sandbox.d.ts.map +1 -0
  54. package/dist/sandbox/docker-image-sandbox.d.ts +32 -0
  55. package/dist/sandbox/docker-image-sandbox.d.ts.map +1 -0
  56. package/dist/sandbox/env-filter.d.ts +19 -0
  57. package/dist/sandbox/env-filter.d.ts.map +1 -0
  58. package/dist/sandbox/index.d.ts +9 -0
  59. package/dist/sandbox/index.d.ts.map +1 -0
  60. package/dist/sandbox/sandbox-manager.d.ts +39 -0
  61. package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
  62. package/dist/sandbox/sandbox-telemetry.d.ts +9 -0
  63. package/dist/sandbox/sandbox-telemetry.d.ts.map +1 -0
  64. package/dist/sandbox/trace-ingester.d.ts +19 -0
  65. package/dist/sandbox/trace-ingester.d.ts.map +1 -0
  66. package/dist/sandbox/types.d.ts +149 -0
  67. package/dist/sandbox/types.d.ts.map +1 -0
  68. package/dist/scheduler/cli-handler.d.ts +6 -0
  69. package/dist/scheduler/cli-handler.d.ts.map +1 -0
  70. package/dist/scheduler/index.d.ts +14 -0
  71. package/dist/scheduler/index.d.ts.map +1 -0
  72. package/dist/scheduler/schedule-parser.d.ts +34 -0
  73. package/dist/scheduler/schedule-parser.d.ts.map +1 -0
  74. package/dist/scheduler/schedule-store.d.ts +182 -0
  75. package/dist/scheduler/schedule-store.d.ts.map +1 -0
  76. package/dist/scheduler/schedule-tool.d.ts +137 -0
  77. package/dist/scheduler/schedule-tool.d.ts.map +1 -0
  78. package/dist/scheduler/scheduler.d.ts +195 -0
  79. package/dist/scheduler/scheduler.d.ts.map +1 -0
  80. package/dist/sdk/check-provider-registry-ACRGIYOB.mjs +28 -0
  81. package/dist/sdk/check-provider-registry-VYHKFHK2.mjs +28 -0
  82. package/dist/sdk/{chunk-CNX7V5JK.mjs → chunk-25IC7KXZ.mjs} +2 -2
  83. package/dist/sdk/{chunk-IHZOSIF4.mjs → chunk-2KB35MB7.mjs} +3 -3
  84. package/dist/sdk/{chunk-HQL734ZI.mjs → chunk-6W75IMDC.mjs} +3 -3
  85. package/dist/sdk/{chunk-XWJPT5KQ.mjs → chunk-7YSOINAQ.mjs} +392 -18
  86. package/dist/sdk/chunk-7YSOINAQ.mjs.map +1 -0
  87. package/dist/sdk/{chunk-3OMWVM6J.mjs → chunk-B7BVQM5K.mjs} +2 -2
  88. package/dist/sdk/chunk-BDGUM6BA.mjs +38825 -0
  89. package/dist/sdk/chunk-BDGUM6BA.mjs.map +1 -0
  90. package/dist/sdk/{chunk-VW2GBXQT.mjs → chunk-D5KI4YQ4.mjs} +3 -3
  91. package/dist/sdk/chunk-DGZPPGJJ.mjs +38825 -0
  92. package/dist/sdk/chunk-DGZPPGJJ.mjs.map +1 -0
  93. package/dist/sdk/chunk-H5BOW5CR.mjs +91 -0
  94. package/dist/sdk/chunk-H5BOW5CR.mjs.map +1 -0
  95. package/dist/sdk/{chunk-YSN4G6CI.mjs → chunk-HEX3RL32.mjs} +81 -3
  96. package/dist/sdk/{chunk-YSN4G6CI.mjs.map → chunk-HEX3RL32.mjs.map} +1 -1
  97. package/dist/sdk/{chunk-ZYAUYXSW.mjs → chunk-J5RGJQ53.mjs} +14 -3
  98. package/dist/sdk/{chunk-ZYAUYXSW.mjs.map → chunk-J5RGJQ53.mjs.map} +1 -1
  99. package/dist/sdk/{chunk-WMJKH4XE.mjs → chunk-J7LXIPZS.mjs} +16 -1
  100. package/dist/sdk/{chunk-EXFGO4FX.mjs → chunk-KFKHU6CM.mjs} +2 -2
  101. package/dist/sdk/{chunk-MPS4HVQI.mjs → chunk-N7HO6KKC.mjs} +8 -8
  102. package/dist/sdk/{chunk-O5EZDNYL.mjs → chunk-NCWIZVOT.mjs} +2 -2
  103. package/dist/sdk/{chunk-3NMLT3YS.mjs → chunk-PO7X5XI7.mjs} +3 -3
  104. package/dist/sdk/{chunk-BHOKBQPB.mjs → chunk-R5Z7YWPB.mjs} +5 -5
  105. package/dist/sdk/{chunk-EORMDOZU.mjs → chunk-SGS2VMEL.mjs} +7 -7
  106. package/dist/sdk/{chunk-BOVFH3LI.mjs → chunk-VF6XIUE4.mjs} +21 -10
  107. package/dist/sdk/chunk-VF6XIUE4.mjs.map +1 -0
  108. package/dist/sdk/{chunk-J2QWVDXK.mjs → chunk-XDLQ3UNF.mjs} +3 -3
  109. package/dist/sdk/{chunk-S2RUE2RG.mjs → chunk-XR7XXGL7.mjs} +3 -3
  110. package/dist/sdk/{chunk-NAW3DB3I.mjs → chunk-XXAEN5KU.mjs} +3 -3
  111. package/dist/sdk/command-executor-DVVXERLR.mjs +14 -0
  112. package/dist/sdk/config-7VTT64SQ.mjs +16 -0
  113. package/dist/sdk/config-merger-RKCZJQ44.mjs +10 -0
  114. package/dist/sdk/event-bus-5K3Y2FCS.mjs +43 -0
  115. package/dist/sdk/{event-bus-5BEVPQ6T.mjs.map → event-bus-5K3Y2FCS.mjs.map} +1 -1
  116. package/dist/sdk/failure-condition-evaluator-4WMDF4Q3.mjs +17 -0
  117. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs +471 -0
  118. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs.map +1 -0
  119. package/dist/sdk/{github-frontend-5PCKKHVC.mjs → github-frontend-3N2NLO66.mjs} +520 -588
  120. package/dist/sdk/github-frontend-3N2NLO66.mjs.map +1 -0
  121. package/dist/sdk/host-ONVMEHAA.mjs +63 -0
  122. package/dist/sdk/host-ONVMEHAA.mjs.map +1 -0
  123. package/dist/sdk/{liquid-extensions-I7O7KMHF.mjs → liquid-extensions-5IZLTFSZ.mjs} +8 -8
  124. package/dist/sdk/memory-store-3N4AZCYB.mjs +12 -0
  125. package/dist/sdk/{metrics-7PP3EJUH.mjs → metrics-GXQ2EDXA.mjs} +4 -4
  126. package/dist/sdk/ndjson-sink-FD2PSXGD.mjs +52 -0
  127. package/dist/sdk/{ndjson-sink-B4V4NTAQ.mjs.map → ndjson-sink-FD2PSXGD.mjs.map} +1 -1
  128. package/dist/sdk/{prompt-state-EZYOUG75.mjs → prompt-state-YHGXB2OA.mjs} +5 -5
  129. package/dist/sdk/{renderer-schema-CKFB5NDB.mjs → renderer-schema-CMXOLNIG.mjs} +4 -4
  130. package/dist/sdk/routing-S3Y7T2X3.mjs +24 -0
  131. package/dist/sdk/sdk.d.mts +212 -4
  132. package/dist/sdk/sdk.d.ts +212 -4
  133. package/dist/sdk/sdk.js +26927 -6264
  134. package/dist/sdk/sdk.js.map +1 -1
  135. package/dist/sdk/sdk.mjs +23 -1278
  136. package/dist/sdk/sdk.mjs.map +1 -1
  137. package/dist/sdk/session-registry-6PV6SGEJ.mjs +10 -0
  138. package/dist/sdk/slack-frontend-R3M2CACB.mjs +899 -0
  139. package/dist/sdk/slack-frontend-R3M2CACB.mjs.map +1 -0
  140. package/dist/sdk/{trace-helpers-VP6QYVBX.mjs → trace-helpers-YHNPC7MR.mjs} +4 -4
  141. package/dist/sdk/tracer-init-XPRWKMZT.mjs +10 -0
  142. package/dist/sdk/tui-frontend-S546M7A7.mjs +281 -0
  143. package/dist/sdk/tui-frontend-S546M7A7.mjs.map +1 -0
  144. package/dist/sdk/workflow-check-provider-4F3432ZP.mjs +28 -0
  145. package/dist/sdk/workflow-check-provider-A44PBPG2.mjs +28 -0
  146. package/dist/sdk/workflow-check-provider-A44PBPG2.mjs.map +1 -0
  147. package/dist/sdk/workflow-registry-ZAYYXLEP.mjs +12 -0
  148. package/dist/sdk/workflow-registry-ZAYYXLEP.mjs.map +1 -0
  149. package/dist/slack/client.d.ts +28 -0
  150. package/dist/slack/client.d.ts.map +1 -1
  151. package/dist/slack/schedule-tool-handler.d.ts +46 -0
  152. package/dist/slack/schedule-tool-handler.d.ts.map +1 -0
  153. package/dist/slack/slack-output-adapter.d.ts +44 -0
  154. package/dist/slack/slack-output-adapter.d.ts.map +1 -0
  155. package/dist/slack/socket-runner.d.ts +22 -0
  156. package/dist/slack/socket-runner.d.ts.map +1 -1
  157. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  158. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -1
  159. package/dist/state-machine/dispatch/sandbox-routing.d.ts +21 -0
  160. package/dist/state-machine/dispatch/sandbox-routing.d.ts.map +1 -0
  161. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  162. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  163. package/dist/test-runner/index.d.ts +5 -0
  164. package/dist/test-runner/index.d.ts.map +1 -1
  165. package/dist/{output/traces/run-2026-02-01T09-59-08-165Z.ndjson → traces/run-2026-02-08T18-16-04-160Z.ndjson} +84 -84
  166. package/dist/{output/traces/run-2026-02-01T09-59-52-595Z.ndjson → traces/run-2026-02-08T18-16-51-253Z.ndjson} +1029 -1029
  167. package/dist/tui/chat-runner.d.ts +39 -0
  168. package/dist/tui/chat-runner.d.ts.map +1 -0
  169. package/dist/tui/chat-state.d.ts +56 -0
  170. package/dist/tui/chat-state.d.ts.map +1 -0
  171. package/dist/tui/chat-tui.d.ts +69 -0
  172. package/dist/tui/chat-tui.d.ts.map +1 -0
  173. package/dist/tui/components/chat-box.d.ts +33 -0
  174. package/dist/tui/components/chat-box.d.ts.map +1 -0
  175. package/dist/tui/components/input-bar.d.ts +50 -0
  176. package/dist/tui/components/input-bar.d.ts.map +1 -0
  177. package/dist/tui/components/status-bar.d.ts +31 -0
  178. package/dist/tui/components/status-bar.d.ts.map +1 -0
  179. package/dist/tui/components/trace-viewer.d.ts +73 -0
  180. package/dist/tui/components/trace-viewer.d.ts.map +1 -0
  181. package/dist/tui/index.d.ts +14 -0
  182. package/dist/tui/index.d.ts.map +1 -0
  183. package/dist/tui/tui-frontend.d.ts +29 -0
  184. package/dist/tui/tui-frontend.d.ts.map +1 -0
  185. package/dist/types/bot.d.ts +35 -0
  186. package/dist/types/bot.d.ts.map +1 -1
  187. package/dist/types/config.d.ts +152 -3
  188. package/dist/types/config.d.ts.map +1 -1
  189. package/dist/types/engine.d.ts +3 -0
  190. package/dist/types/engine.d.ts.map +1 -1
  191. package/dist/utils/sandbox.d.ts.map +1 -1
  192. package/dist/utils/workspace-manager.d.ts +22 -2
  193. package/dist/utils/workspace-manager.d.ts.map +1 -1
  194. package/dist/utils/worktree-manager.d.ts +2 -1
  195. package/dist/utils/worktree-manager.d.ts.map +1 -1
  196. package/package.json +4 -2
  197. package/dist/docs/NPM_USAGE.md +0 -281
  198. package/dist/generated/config-schema.json +0 -2161
  199. package/dist/sdk/check-provider-registry-CVUONJ5A.mjs +0 -28
  200. package/dist/sdk/chunk-BOVFH3LI.mjs.map +0 -1
  201. package/dist/sdk/chunk-TS6BUNAI.mjs +0 -17722
  202. package/dist/sdk/chunk-TS6BUNAI.mjs.map +0 -1
  203. package/dist/sdk/chunk-XWJPT5KQ.mjs.map +0 -1
  204. package/dist/sdk/command-executor-Q7MHJKZJ.mjs +0 -14
  205. package/dist/sdk/config-DXX64GD3.mjs +0 -16
  206. package/dist/sdk/config-merger-PX3WIT57.mjs +0 -10
  207. package/dist/sdk/event-bus-5BEVPQ6T.mjs +0 -35
  208. package/dist/sdk/failure-condition-evaluator-G4HMJPXF.mjs +0 -17
  209. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs +0 -458
  210. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs.map +0 -1
  211. package/dist/sdk/github-frontend-5PCKKHVC.mjs.map +0 -1
  212. package/dist/sdk/host-H3AWNZ2F.mjs +0 -52
  213. package/dist/sdk/host-H3AWNZ2F.mjs.map +0 -1
  214. package/dist/sdk/memory-store-RW5N2NGJ.mjs +0 -12
  215. package/dist/sdk/ndjson-sink-B4V4NTAQ.mjs +0 -44
  216. package/dist/sdk/routing-QHTGDIXF.mjs +0 -24
  217. package/dist/sdk/session-registry-4E6YRQ77.mjs +0 -10
  218. package/dist/sdk/slack-frontend-JUT3TYVC.mjs +0 -821
  219. package/dist/sdk/slack-frontend-JUT3TYVC.mjs.map +0 -1
  220. package/dist/sdk/tracer-init-GSLPPLCD.mjs +0 -10
  221. package/dist/sdk/workflow-check-provider-3IWBAZP7.mjs +0 -28
  222. package/dist/sdk/workflow-registry-KFWSDSLM.mjs +0 -12
  223. package/dist/tui.d.ts +0 -51
  224. package/dist/tui.d.ts.map +0 -1
  225. /package/dist/sdk/{check-provider-registry-CVUONJ5A.mjs.map → check-provider-registry-ACRGIYOB.mjs.map} +0 -0
  226. /package/dist/sdk/{chunk-WMJKH4XE.mjs.map → check-provider-registry-VYHKFHK2.mjs.map} +0 -0
  227. /package/dist/sdk/{chunk-CNX7V5JK.mjs.map → chunk-25IC7KXZ.mjs.map} +0 -0
  228. /package/dist/sdk/{chunk-IHZOSIF4.mjs.map → chunk-2KB35MB7.mjs.map} +0 -0
  229. /package/dist/sdk/{chunk-HQL734ZI.mjs.map → chunk-6W75IMDC.mjs.map} +0 -0
  230. /package/dist/sdk/{chunk-3OMWVM6J.mjs.map → chunk-B7BVQM5K.mjs.map} +0 -0
  231. /package/dist/sdk/{chunk-VW2GBXQT.mjs.map → chunk-D5KI4YQ4.mjs.map} +0 -0
  232. /package/dist/sdk/{command-executor-Q7MHJKZJ.mjs.map → chunk-J7LXIPZS.mjs.map} +0 -0
  233. /package/dist/sdk/{chunk-EXFGO4FX.mjs.map → chunk-KFKHU6CM.mjs.map} +0 -0
  234. /package/dist/sdk/{chunk-MPS4HVQI.mjs.map → chunk-N7HO6KKC.mjs.map} +0 -0
  235. /package/dist/sdk/{chunk-O5EZDNYL.mjs.map → chunk-NCWIZVOT.mjs.map} +0 -0
  236. /package/dist/sdk/{chunk-3NMLT3YS.mjs.map → chunk-PO7X5XI7.mjs.map} +0 -0
  237. /package/dist/sdk/{chunk-BHOKBQPB.mjs.map → chunk-R5Z7YWPB.mjs.map} +0 -0
  238. /package/dist/sdk/{chunk-EORMDOZU.mjs.map → chunk-SGS2VMEL.mjs.map} +0 -0
  239. /package/dist/sdk/{chunk-J2QWVDXK.mjs.map → chunk-XDLQ3UNF.mjs.map} +0 -0
  240. /package/dist/sdk/{chunk-S2RUE2RG.mjs.map → chunk-XR7XXGL7.mjs.map} +0 -0
  241. /package/dist/sdk/{chunk-NAW3DB3I.mjs.map → chunk-XXAEN5KU.mjs.map} +0 -0
  242. /package/dist/sdk/{config-DXX64GD3.mjs.map → command-executor-DVVXERLR.mjs.map} +0 -0
  243. /package/dist/sdk/{config-merger-PX3WIT57.mjs.map → config-7VTT64SQ.mjs.map} +0 -0
  244. /package/dist/sdk/{failure-condition-evaluator-G4HMJPXF.mjs.map → config-merger-RKCZJQ44.mjs.map} +0 -0
  245. /package/dist/sdk/{liquid-extensions-I7O7KMHF.mjs.map → failure-condition-evaluator-4WMDF4Q3.mjs.map} +0 -0
  246. /package/dist/sdk/{memory-store-RW5N2NGJ.mjs.map → liquid-extensions-5IZLTFSZ.mjs.map} +0 -0
  247. /package/dist/sdk/{metrics-7PP3EJUH.mjs.map → memory-store-3N4AZCYB.mjs.map} +0 -0
  248. /package/dist/sdk/{prompt-state-EZYOUG75.mjs.map → metrics-GXQ2EDXA.mjs.map} +0 -0
  249. /package/dist/sdk/{routing-QHTGDIXF.mjs.map → prompt-state-YHGXB2OA.mjs.map} +0 -0
  250. /package/dist/sdk/{renderer-schema-CKFB5NDB.mjs.map → renderer-schema-CMXOLNIG.mjs.map} +0 -0
  251. /package/dist/sdk/{session-registry-4E6YRQ77.mjs.map → routing-S3Y7T2X3.mjs.map} +0 -0
  252. /package/dist/sdk/{trace-helpers-VP6QYVBX.mjs.map → session-registry-6PV6SGEJ.mjs.map} +0 -0
  253. /package/dist/sdk/{tracer-init-GSLPPLCD.mjs.map → trace-helpers-YHNPC7MR.mjs.map} +0 -0
  254. /package/dist/sdk/{workflow-check-provider-3IWBAZP7.mjs.map → tracer-init-XPRWKMZT.mjs.map} +0 -0
  255. /package/dist/sdk/{workflow-registry-KFWSDSLM.mjs.map → workflow-check-provider-4F3432ZP.mjs.map} +0 -0
@@ -0,0 +1,503 @@
1
+ # Scheduler
2
+
3
+ The Visor scheduler provides a generic, frontend-agnostic system for executing workflows and reminders at specified times. It supports both static schedules defined in YAML configuration and dynamic schedules created via AI tool at runtime.
4
+
5
+ ## Overview
6
+
7
+ The scheduler operates in two modes:
8
+
9
+ 1. **Workflow Schedules**: Execute a named workflow/check from your configuration
10
+ 2. **Simple Reminders**: Post a message or run it through the visor pipeline (e.g., for AI-powered responses)
11
+
12
+ Output destinations (Slack, GitHub, webhooks) are handled by **output adapters**, making the scheduler truly frontend-agnostic. When running with Slack, the `SlackOutputAdapter` automatically posts results back to the appropriate channel or DM.
13
+
14
+ ## Configuration
15
+
16
+ Add scheduler settings to your `.visor.yaml`:
17
+
18
+ ```yaml
19
+ scheduler:
20
+ enabled: true
21
+ storage:
22
+ path: .visor/schedules.json
23
+ default_timezone: America/New_York
24
+ check_interval_ms: 60000 # How often to check for due schedules
25
+
26
+ # Limits for dynamic schedules (created via AI tool)
27
+ limits:
28
+ max_per_user: 25
29
+ max_recurring_per_user: 10
30
+ max_global: 1000
31
+
32
+ # Permissions for dynamic schedule creation
33
+ permissions:
34
+ allow_personal: true # Allow schedules via DM or CLI
35
+ allow_channel: true # Allow channel schedules (Slack)
36
+ allow_dm: true # Allow DM schedules to other users
37
+ allowed_workflows: # Glob patterns for allowed workflows
38
+ - "report-*"
39
+ - "status-*"
40
+ denied_workflows: # Glob patterns for denied workflows
41
+ - "admin-*"
42
+ - "dangerous-*"
43
+
44
+ # Static cron jobs (always allowed, bypass permissions)
45
+ cron:
46
+ daily-standup:
47
+ schedule: "0 9 * * 1-5" # Weekdays at 9am
48
+ workflow: daily-standup
49
+ timezone: America/New_York
50
+ output:
51
+ type: slack
52
+ target: "#engineering"
53
+
54
+ weekly-report:
55
+ schedule: "0 10 * * 1" # Mondays at 10am
56
+ workflow: weekly-report
57
+ inputs:
58
+ team: platform
59
+ output:
60
+ type: slack
61
+ target: "#platform-team"
62
+ ```
63
+
64
+ ## Static Cron Jobs
65
+
66
+ Static cron jobs are defined in your configuration file and always run regardless of permission settings. They're ideal for recurring organizational tasks:
67
+
68
+ ```yaml
69
+ scheduler:
70
+ cron:
71
+ security-scan:
72
+ schedule: "0 2 * * *" # Daily at 2am
73
+ workflow: security-scan
74
+ output:
75
+ type: slack
76
+ target: "#security-alerts"
77
+
78
+ backup-status:
79
+ schedule: "0 6 * * *" # Daily at 6am
80
+ workflow: backup-check
81
+ inputs:
82
+ notify_on_failure: true
83
+ ```
84
+
85
+ ### Cron Expression Format
86
+
87
+ Standard 5-field cron expressions:
88
+ - `* * * * *` - minute, hour, day of month, month, day of week
89
+ - `0 9 * * *` - Every day at 9:00 AM
90
+ - `0 9 * * 1-5` - Weekdays at 9:00 AM
91
+ - `*/15 * * * *` - Every 15 minutes
92
+ - `0 0 1 * *` - First day of every month at midnight
93
+
94
+ ## Dynamic Schedules (AI Tool)
95
+
96
+ Users can create schedules dynamically through the AI tool. The AI is responsible for:
97
+
98
+ 1. **Extracting timing**: Converting natural language to cron expressions or ISO timestamps
99
+ 2. **Determining targets**: Using the current channel context (channel ID from conversation)
100
+ 3. **Identifying recurrence**: One-time vs recurring schedules
101
+
102
+ ### Example Interactions
103
+
104
+ ```
105
+ User in DM: "remind me to check builds every day at 9am"
106
+ AI: [calls schedule tool with action=create, reminder_text="check builds",
107
+ cron="0 9 * * *", target_type="dm", target_id="D09SZABNLG3"]
108
+
109
+ User in #security: "run security-scan every Monday at 10am"
110
+ AI: [calls schedule tool with action=create, workflow="security-scan",
111
+ cron="0 10 * * 1", target_type="channel", target_id="C05ABC123"]
112
+
113
+ User in DM: "remind me in 2 hours to review the PR"
114
+ AI: [calls schedule tool with action=create, reminder_text="review the PR",
115
+ run_at="2026-02-08T18:00:00Z", target_type="dm", target_id="D09SZABNLG3"]
116
+ ```
117
+
118
+ ### AI Tool Parameters
119
+
120
+ The AI generates structured parameters:
121
+
122
+ | Parameter | Description |
123
+ |-----------|-------------|
124
+ | `reminder_text` | What to say when the schedule fires |
125
+ | `workflow` | Alternatively, a workflow to execute |
126
+ | `target_type` | "channel", "dm", "thread", or "user" |
127
+ | `target_id` | Slack channel ID (C... or D...) |
128
+ | `cron` | For recurring: cron expression |
129
+ | `run_at` | For one-time: ISO 8601 timestamp |
130
+ | `is_recurring` | Boolean flag |
131
+
132
+ ### Permission Controls
133
+
134
+ Dynamic schedules respect the `permissions` configuration:
135
+
136
+ - **allow_personal**: Controls personal schedules (DM context or CLI)
137
+ - **allow_channel**: Controls channel schedules (Slack channels)
138
+ - **allow_dm**: Controls DM schedules to other users
139
+ - **allowed_workflows**: Glob patterns that workflows must match
140
+ - **denied_workflows**: Glob patterns that block workflows (checked first)
141
+
142
+ ```yaml
143
+ permissions:
144
+ allow_personal: true
145
+ allow_channel: false # Disable channel schedules
146
+ allow_dm: false # Disable DM schedules
147
+ allowed_workflows:
148
+ - "report-*" # Only allow report workflows
149
+ ```
150
+
151
+ ### Schedule Types and Context Restrictions
152
+
153
+ The scheduler determines schedule type based on context and enforces restrictions:
154
+
155
+ | Context | Allowed Schedule Type |
156
+ |---------|----------------------|
157
+ | CLI | personal only |
158
+ | Slack DM | personal only |
159
+ | Slack channel | channel only |
160
+ | Slack group DM | dm only |
161
+
162
+ **Context-Based Enforcement**: When creating a schedule from a DM, you can only create personal schedules. When in a channel, you can only create channel schedules. This prevents cross-context leakage (e.g., personal reminders shouldn't appear when listing schedules in a public channel).
163
+
164
+ ### List Filtering
165
+
166
+ When listing schedules, only schedules matching the current context type are shown:
167
+ - In a DM: Only personal schedules
168
+ - In a channel: Only channel schedules
169
+ - In a group DM: Only dm/group schedules
170
+
171
+ This protects privacy - personal reminders created in a DM won't be visible when someone lists schedules in a public channel.
172
+
173
+ ## CLI Commands
174
+
175
+ ### Start the Scheduler Daemon
176
+
177
+ ```bash
178
+ visor schedule start [--config .visor.yaml]
179
+ ```
180
+
181
+ Runs the scheduler daemon that checks for and executes due schedules.
182
+
183
+ ### List Schedules
184
+
185
+ ```bash
186
+ visor schedule list [--user <userId>] [--status <status>] [--json]
187
+ ```
188
+
189
+ Shows schedules. Use `--json` for machine-readable output.
190
+
191
+ ### Create a Schedule
192
+
193
+ ```bash
194
+ visor schedule create <workflow> --at "<expression>" [--inputs key=value] [--output-type slack] [--output-target #channel]
195
+ ```
196
+
197
+ Examples:
198
+ ```bash
199
+ # One-time schedule
200
+ visor schedule create daily-report --at "tomorrow at 9am"
201
+
202
+ # Recurring schedule
203
+ visor schedule create standup --at "every weekday at 9am" --output-type slack --output-target "#team"
204
+
205
+ # With inputs
206
+ visor schedule create backup-check --at "every day at 2am" --inputs environment=production
207
+ ```
208
+
209
+ ### Cancel a Schedule
210
+
211
+ ```bash
212
+ visor schedule cancel <id>
213
+ ```
214
+
215
+ ### Pause/Resume
216
+
217
+ ```bash
218
+ visor schedule pause <id>
219
+ visor schedule resume <id>
220
+ ```
221
+
222
+ ## Output Adapters
223
+
224
+ When a schedule executes, results can be routed to different destinations:
225
+
226
+ ### Slack Output
227
+
228
+ ```yaml
229
+ output:
230
+ type: slack
231
+ target: "#channel-name" # or @username for DM
232
+ thread_id: "1234567890.123456" # Optional thread
233
+ ```
234
+
235
+ ### GitHub Output
236
+
237
+ ```yaml
238
+ output:
239
+ type: github
240
+ target: "owner/repo"
241
+ ```
242
+
243
+ ### Webhook Output
244
+
245
+ ```yaml
246
+ output:
247
+ type: webhook
248
+ target: "https://example.com/webhook"
249
+ ```
250
+
251
+ ### No Output
252
+
253
+ ```yaml
254
+ output:
255
+ type: none
256
+ ```
257
+
258
+ ## Integration with Slack
259
+
260
+ When running the Slack bot, the scheduler automatically starts and integrates with the Slack frontend:
261
+
262
+ ```typescript
263
+ // In socket-runner.ts
264
+ import { Scheduler, createSlackOutputAdapter } from '../scheduler';
265
+
266
+ // Create scheduler
267
+ this.genericScheduler = new Scheduler(this.visorConfig, schedulerConfig);
268
+
269
+ // Set execution context so scheduled reminders can use the Slack client
270
+ this.genericScheduler.setExecutionContext({
271
+ slack: this.client,
272
+ slackClient: this.client,
273
+ });
274
+
275
+ // Register Slack output adapter for posting results
276
+ this.genericScheduler.registerOutputAdapter(
277
+ createSlackOutputAdapter(this.client)
278
+ );
279
+
280
+ await this.genericScheduler.start();
281
+ ```
282
+
283
+ ### Execution Context
284
+
285
+ The scheduler uses an execution context to pass runtime dependencies to workflow executions:
286
+
287
+ - **`slackClient`**: The Slack API client for posting messages
288
+ - **`cliMessage`**: For simple reminders, this bypasses `human-input` prompts
289
+
290
+ ### First Message Seeding
291
+
292
+ For simple reminders, the scheduler seeds the `PromptStateManager` so that `human-input` checks can consume the reminder text as if the user sent it:
293
+
294
+ ```typescript
295
+ const mgr = getPromptStateManager();
296
+ mgr.setFirstMessage(channel, threadTs, reminderText);
297
+ ```
298
+
299
+ This ensures reminders run through chat workflows smoothly without blocking for user input.
300
+
301
+ ## Natural Language Parsing
302
+
303
+ The scheduler understands various time expressions:
304
+
305
+ ### One-time
306
+ - "in 2 hours"
307
+ - "in 30 minutes"
308
+ - "tomorrow at 9am"
309
+ - "next Monday at 3pm"
310
+ - "Friday at noon"
311
+
312
+ ### Recurring
313
+ - "every day at 9am"
314
+ - "every Monday at 9am"
315
+ - "every weekday at 9am"
316
+ - "every hour"
317
+ - "every 30 minutes"
318
+ - "every month on the 1st at midnight"
319
+
320
+ ## Schedule Lifecycle
321
+
322
+ 1. **Created**: Schedule is stored with status `active`
323
+ 2. **Due**: When current time >= nextRunAt, schedule is picked up
324
+ 3. **Executing**: Workflow runs with schedule context
325
+ 4. **Completed**:
326
+ - One-time: status changes to `completed`
327
+ - Recurring: `nextRunAt` is updated, status stays `active`
328
+ 5. **Paused**: Schedule is skipped during checks
329
+ 6. **Failed**: Increments `failureCount`, may be retried
330
+
331
+ ## Simple Reminders (No Workflow)
332
+
333
+ When a schedule has no `workflow` specified but includes `workflowInputs.text`, it runs as a "simple reminder":
334
+
335
+ 1. The reminder text is treated as if the user sent it as a new message
336
+ 2. It runs through the full visor pipeline (all configured checks)
337
+ 3. The AI processes it and posts the response back via the Slack frontend
338
+ 4. The `SlackOutputAdapter` detects when the pipeline handled output and avoids double-posting
339
+
340
+ This allows reminders like "check how many Jira tickets were created this week" to get an AI-generated response rather than just echoing the reminder text.
341
+
342
+ ```yaml
343
+ # Example: Schedule created via AI tool
344
+ # When this fires, it runs through the pipeline and posts the AI response
345
+ {
346
+ "workflowInputs": { "text": "How many PRs were merged today?" },
347
+ "outputContext": { "type": "slack", "target": "D09SZABNLG3" }
348
+ }
349
+ ```
350
+
351
+ ### Response Continuity (previousResponse)
352
+
353
+ For **recurring** simple reminders, the scheduler saves the AI response after each execution. On subsequent runs, the previous response is included in the context, allowing the AI to reference or build upon its earlier answer.
354
+
355
+ **How it works:**
356
+ 1. Reminder fires and runs through the visor pipeline
357
+ 2. AI generates a response, which is posted to Slack
358
+ 3. The response text is saved as `previousResponse` in the schedule store
359
+ 4. On the next run, the reminder text includes the previous response:
360
+ ```
361
+ <original reminder text>
362
+
363
+ ---
364
+ **Previous Response (for context):**
365
+ <AI's previous response>
366
+ ---
367
+
368
+ Please provide an updated response based on the reminder above.
369
+ ```
370
+
371
+ **Example use case:**
372
+ ```
373
+ User: "Every day at 9am, tell me how many Jira tickets were created"
374
+
375
+ Day 1: AI responds with "5 tickets were created yesterday"
376
+ Day 2: AI sees previous response and can say "8 tickets today (up from 5 yesterday)"
377
+ ```
378
+
379
+ This feature enables continuity for status updates, progress tracking, and any recurring reminder where historical context is valuable. The AI can:
380
+ - Compare current data to previous runs
381
+ - Track trends over time
382
+ - Provide delta/change information
383
+ - Reference what was said before
384
+
385
+ **Note:** One-time schedules do not save previousResponse since they only execute once.
386
+
387
+ ## Architecture
388
+
389
+ ### File Structure
390
+
391
+ ```
392
+ src/
393
+ ├── scheduler/ # Generic scheduler module
394
+ │ ├── index.ts # Public exports
395
+ │ ├── schedule-store.ts # JSON persistence for schedules
396
+ │ ├── schedule-parser.ts # Natural language parsing utilities
397
+ │ ├── scheduler.ts # Generic scheduler daemon
398
+ │ ├── schedule-tool.ts # AI tool for schedule management
399
+ │ └── cli-handler.ts # CLI command handlers
400
+
401
+ └── slack/
402
+ └── slack-output-adapter.ts # Posts results to Slack
403
+ ```
404
+
405
+ ### Key Components
406
+
407
+ | Component | Purpose |
408
+ |-----------|---------|
409
+ | `Scheduler` | Main daemon that checks for due schedules and executes them |
410
+ | `ScheduleStore` | Singleton for persisting schedules to JSON |
411
+ | `ScheduleOutputAdapter` | Interface for output destinations |
412
+ | `SlackOutputAdapter` | Implements output posting for Slack |
413
+ | `schedule-tool` | AI tool definition and handler |
414
+
415
+ ### Execution Flow
416
+
417
+ 1. User creates schedule via AI tool or CLI
418
+ 2. `ScheduleStore.create()` persists schedule
419
+ 3. `Scheduler` either sets up cron job (recurring) or setTimeout (one-time)
420
+ 4. When schedule fires:
421
+ - With workflow: Runs named workflow via `StateMachineExecutionEngine`
422
+ - Without workflow: Runs reminder text through full visor pipeline
423
+ 5. `SlackOutputAdapter.sendResult()` posts results (unless pipeline already handled it)
424
+
425
+ ## Troubleshooting
426
+
427
+ ### Schedule Not Running
428
+
429
+ 1. Check scheduler is running: `visor schedule start`
430
+ 2. Verify schedule status: `visor schedule list`
431
+ 3. Check workflow exists in config
432
+ 4. Verify permissions allow the schedule type
433
+
434
+ ### Reminder Not Posting to Slack
435
+
436
+ 1. Verify the execution context includes Slack client:
437
+ ```typescript
438
+ scheduler.setExecutionContext({ slack: client, slackClient: client });
439
+ ```
440
+ 2. Check that `SlackOutputAdapter` is registered:
441
+ ```typescript
442
+ scheduler.registerOutputAdapter(createSlackOutputAdapter(client));
443
+ ```
444
+ 3. For simple reminders, verify the pipeline has a `human-input` check and AI check
445
+ 4. Check logs for `[SlackOutputAdapter] Skipping post` - this means the pipeline already handled output
446
+
447
+ ### Personal Reminders Showing in Channel
448
+
449
+ If personal reminders appear when listing in a channel, ensure:
450
+ 1. The `allowedScheduleType` context is being set correctly based on channel type
451
+ 2. The schedule's `outputContext.target` correctly identifies the channel type
452
+
453
+ ### Permission Denied
454
+
455
+ 1. Check `permissions` config matches schedule type
456
+ 2. Verify workflow matches `allowed_workflows` patterns
457
+ 3. Ensure workflow doesn't match `denied_workflows`
458
+
459
+ ### Timezone Issues
460
+
461
+ 1. Set explicit timezone in config: `default_timezone: America/New_York`
462
+ 2. User timezone is captured when schedule is created
463
+ 3. All times stored as UTC internally
464
+
465
+ ## API Reference
466
+
467
+ ### Schedule Interface
468
+
469
+ ```typescript
470
+ interface Schedule {
471
+ id: string; // UUID v4
472
+ creatorId: string; // User who created
473
+ creatorName?: string; // User display name (for messages)
474
+ creatorContext?: string; // "slack:U123", "github:user", "cli"
475
+ timezone: string; // IANA timezone
476
+ schedule: string; // Cron expression (empty for one-time)
477
+ runAt?: number; // Unix timestamp (one-time only)
478
+ isRecurring: boolean;
479
+ originalExpression: string; // Natural language input (for display)
480
+ workflow?: string; // Workflow/check ID (undefined for simple reminders)
481
+ workflowInputs?: Record<string, unknown>; // For reminders: { text: "..." }
482
+ outputContext?: ScheduleOutputContext;
483
+ status: 'active' | 'paused' | 'completed' | 'failed';
484
+ nextRunAt?: number;
485
+ lastRunAt?: number;
486
+ runCount: number;
487
+ failureCount: number;
488
+ lastError?: string; // Last error message if failed
489
+ previousResponse?: string; // AI response from last run (recurring only)
490
+ createdAt: number; // Creation timestamp
491
+ }
492
+ ```
493
+
494
+ ### ScheduleOutputContext
495
+
496
+ ```typescript
497
+ interface ScheduleOutputContext {
498
+ type: 'slack' | 'github' | 'webhook' | 'none';
499
+ target?: string;
500
+ threadId?: string;
501
+ metadata?: Record<string, unknown>;
502
+ }
503
+ ```
@@ -122,6 +122,11 @@ slack:
122
122
  # to mention/threads/channel allowlist gating).
123
123
  allow_bot_messages: false
124
124
 
125
+ # Optional: allow guest users (default: false)
126
+ # When false, single-channel and multi-channel guests are ignored.
127
+ # Set to true to allow guest users to interact with the bot.
128
+ allow_guests: false
129
+
125
130
  # Optional: rate limiting
126
131
  rate_limiting:
127
132
  enabled: true
@@ -163,6 +168,7 @@ checks:
163
168
  | `threads` | string | `"any"` | `"required"` to only respond in threads |
164
169
  | `channel_allowlist` | string[] | `[]` | Limit to specific channels (supports `*` wildcard) |
165
170
  | `allow_bot_messages` | boolean | `false` | Allow `bot_message` events to trigger runs |
171
+ | `allow_guests` | boolean | `false` | Allow guest users (single/multi-channel) to trigger runs |
166
172
  | `show_raw_output` | boolean | `false` | Post raw JSON output (for debugging) |
167
173
  | `reactions.enabled` | boolean | `true` | Enable reaction management |
168
174
  | `reactions.ack` | string | `"eyes"` | Reaction name for acknowledgement |
@@ -468,8 +474,23 @@ Log messages include:
468
474
  - `[slack-frontend]` - Message posting and reactions
469
475
  - `[prompt-state]` - Human input state management
470
476
 
477
+ ## Scheduled Reminders
478
+
479
+ The scheduler integrates with Slack to deliver scheduled reminders and workflow results. See [Scheduler](./scheduler.md) for full documentation.
480
+
481
+ Quick example:
482
+ ```
483
+ User: "remind me to check PRs every day at 9am"
484
+ Bot: Schedule created! Will remind you every day at 9am.
485
+ ```
486
+
487
+ When the schedule fires:
488
+ 1. For simple reminders: The text runs through the visor pipeline, and the AI response is posted back
489
+ 2. For workflow schedules: The workflow executes and results are posted via `SlackOutputAdapter`
490
+
471
491
  ## Related Documentation
472
492
 
493
+ - [Scheduler](./scheduler.md) - Schedule workflows and reminders
473
494
  - [Human Input Provider](./human-input-provider.md) - Detailed documentation on the `human-input` check type
474
495
  - [Bot Transports RFC](./bot-transports-rfc.md) - Technical design document for Slack integration
475
496
  - [Recipes](./recipes.md) - Common workflow patterns including chat loops
@@ -140,7 +140,7 @@ The CLI supports a global timeout via the `--timeout` flag:
140
140
  visor --check all --timeout 300000 # 5 minute global timeout (milliseconds)
141
141
  ```
142
142
 
143
- This timeout applies to AI operations. The default is 1200000ms (20 minutes).
143
+ This timeout applies to AI operations. The default is 1200000ms (30 minutes).
144
144
 
145
145
  ## Examples
146
146
 
@@ -246,6 +246,12 @@ steps:
246
246
 
247
247
  Output structure: `{ text: string, ts: number }`
248
248
 
249
+ > **Tip:** Use `--tui` mode for interactive prompts and real-time visualization:
250
+ > ```bash
251
+ > visor --tui --config workflow.yaml
252
+ > ```
253
+ > TUI provides a chat-style interface for human-input prompts. See [Interactive TUI Mode](#interactive-tui-mode).
254
+
249
255
  ### 5. Log Check (`type: log`)
250
256
 
251
257
  Output messages to the console/log. Useful for debugging workflows and displaying execution information.
@@ -1333,6 +1339,39 @@ visor test --validate
1333
1339
  visor --config workflow.yaml --event pr_opened
1334
1340
  ```
1335
1341
 
1342
+ ### Interactive TUI Mode
1343
+
1344
+ TUI mode provides a persistent terminal interface for running any workflow:
1345
+
1346
+ ```bash
1347
+ # Start interactive TUI
1348
+ visor --tui --config workflow.yaml
1349
+
1350
+ # TUI with debug logging
1351
+ visor --tui --config workflow.yaml --debug
1352
+ ```
1353
+
1354
+ **TUI Tabs:**
1355
+ - **Chat (1)**: Interactive prompts and workflow results
1356
+ - **Logs (2)**: Execution logs and debug output
1357
+ - **Traces (3)**: Real-time OpenTelemetry execution tree
1358
+
1359
+ **Key Bindings:**
1360
+ | Key | Action |
1361
+ |-----|--------|
1362
+ | `Shift+Tab` | Cycle between tabs |
1363
+ | `1` / `2` / `3` | Jump to Chat / Logs / Traces |
1364
+ | `e` | Toggle engine states in Traces tab |
1365
+ | `Enter` | Submit input |
1366
+ | `Ctrl+C` | Abort workflow |
1367
+ | `q` | Exit (when complete) |
1368
+
1369
+ The Traces tab shows:
1370
+ - Execution tree with check hierarchy
1371
+ - forEach iterations grouped under parent
1372
+ - IN/OUT/ERR lines for each span
1373
+ - Press `e` to show/hide engine internals (LevelDispatch, WavePlanning)
1374
+
1336
1375
  ---
1337
1376
 
1338
1377
  ## Related Documentation
@@ -126,6 +126,14 @@ Example configurations demonstrating various Visor features and use cases.
126
126
  - **`failure-conditions-github-style.yaml`** - GitHub-style failure conditions
127
127
  - **`failure-conditions-migration.yaml`** - Migration patterns for failure conditions
128
128
 
129
+ ### Sandbox Examples
130
+ - **`sandbox-basic.yaml`** - Basic Docker sandbox with image mode
131
+ - **`sandbox-cache.yaml`** - Sandbox with cache volume mounts
132
+ - **`sandbox-dockerfile-inline.yaml`** - Sandbox with inline Dockerfile
133
+ - **`sandbox-env-passthrough.yaml`** - Sandbox environment variable passthrough
134
+ - **`sandbox-multi-env.yaml`** - Multiple sandbox environments per check
135
+ - **`sandbox-read-only.yaml`** - Read-only sandbox with network isolation
136
+
129
137
  ### Integration Examples
130
138
  - **`jira-simple-example.yaml`** - Simple JIRA integration
131
139
  - **`jira-single-issue-workflow.yaml`** - Single JIRA issue workflow
@@ -406,6 +414,28 @@ visor --config examples/bedrock-config.yaml --provider bedrock
406
414
  visor --config examples/claude-code-config.yaml
407
415
  ```
408
416
 
417
+ ## 🐳 Sandbox Examples
418
+
419
+ ```bash
420
+ # Basic Docker sandbox
421
+ visor --config examples/sandbox-basic.yaml
422
+
423
+ # Sandbox with cache volumes
424
+ visor --config examples/sandbox-cache.yaml
425
+
426
+ # Inline Dockerfile sandbox
427
+ visor --config examples/sandbox-dockerfile-inline.yaml
428
+
429
+ # Environment variable passthrough
430
+ visor --config examples/sandbox-env-passthrough.yaml
431
+
432
+ # Multiple sandbox environments
433
+ visor --config examples/sandbox-multi-env.yaml
434
+
435
+ # Read-only sandbox with network isolation
436
+ visor --config examples/sandbox-read-only.yaml
437
+ ```
438
+
409
439
  ## 🔧 MCP & Tools Examples
410
440
 
411
441
  ```bash
@@ -38,7 +38,7 @@ checks:
38
38
  operation: set
39
39
  namespace: calculator
40
40
  key: number1
41
- value_js: "parseFloat(outputs['get-number1'])"
41
+ value_js: "parseFloat(outputs['get-number1'].text)"
42
42
 
43
43
  # Step 3: Get second number
44
44
  get-number2:
@@ -55,7 +55,7 @@ checks:
55
55
  operation: set
56
56
  namespace: calculator
57
57
  key: number2
58
- value_js: "parseFloat(outputs['get-number2'])"
58
+ value_js: "parseFloat(outputs['get-number2'].text)"
59
59
 
60
60
  # Step 5: Get operation
61
61
  get-operation:
@@ -72,7 +72,7 @@ checks:
72
72
  operation: set
73
73
  namespace: calculator
74
74
  key: operation
75
- value_js: "outputs['get-operation'].trim()"
75
+ value_js: "outputs['get-operation'].text.trim()"
76
76
 
77
77
  # Step 7: Perform calculation using memory and JavaScript
78
78
  calculate:
@@ -122,7 +122,7 @@ checks:
122
122
  ║ CALCULATION RESULT ║
123
123
  ╠════════════════════════════════════════╣
124
124
  ║ ║
125
- ║ {{ outputs['get-number1'] }} {{ outputs['get-operation'] }} {{ outputs['get-number2'] }} = {{ outputs['calculate'] }}
125
+ ║ {{ outputs['get-number1'].text }} {{ outputs['get-operation'].text }} {{ outputs['get-number2'].text }} = {{ outputs['calculate'] }}
126
126
  ║ ║
127
127
  ╚════════════════════════════════════════╝
128
128