diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index e3a74a567..121831686 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -404,27 +404,15 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
         logger.info { "Syncing friend message history: Success" }
     }
 
-    private suspend fun doHeartBeat(): Exception? {
-        val lastException: Exception?
-        try {
-            kotlin.runCatching {
-                Heartbeat.Alive(bot.client)
-                    .sendAndExpect<Heartbeat.Alive.Response>(
-                        timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
-                        retry = 2
-                    )
-                return null
-            }
+    private suspend fun doHeartBeat(): Throwable? {
+        return retryCatching(2) {
             Heartbeat.Alive(bot.client)
                 .sendAndExpect<Heartbeat.Alive.Response>(
                     timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
                     retry = 2
                 )
             return null
-        } catch (e: Exception) {
-            lastException = e
-        }
-        return lastException
+        }.exceptionOrNull()
     }
 
     /**
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
index cc194bfd2..688db6da5 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt
@@ -24,6 +24,7 @@ internal object MessageSvcPushForceOffline :
     OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
     override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
         val struct = this.readUniPacket(RequestPushForceOffline.serializer())
+        @Suppress("INVISIBLE_MEMBER")
         return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
     }
 }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
index 4ec673d9e..11e0a9912 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
@@ -189,6 +189,7 @@ internal class StatSvc {
 
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): BotOfflineEvent.Dropped {
             val decodeUniPacket = readUniPacket(RequestMSFForceOffline.serializer())
+            @Suppress("INVISIBLE_MEMBER")
             return BotOfflineEvent.Dropped(bot, MsfOfflineToken(decodeUniPacket.uin, decodeUniPacket.iSeqno, 0))
         }
 
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
index 791a1b8b8..2c9e8167f 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -7,15 +7,14 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
-@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport",
-    "EXPERIMENTAL_OVERRIDE")
+@file:Suppress(
+    "EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport",
+    "EXPERIMENTAL_OVERRIDE"
+)
 
 package net.mamoe.mirai
 
-import kotlinx.coroutines.CoroutineName
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.*
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
 import net.mamoe.mirai.event.events.MemberJoinRequestEvent
@@ -35,13 +34,12 @@ import kotlin.jvm.JvmSynthetic
  */
 @JvmSynthetic
 suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
-// 任何人都能看到这个方法
 
 /**
  * 机器人对象. 一个机器人实例登录一个 QQ 账号.
  * Mirai 为多账号设计, 可同时维护多个机器人.
  *
- * 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出.
+ * 有关 [Bot] 生命管理, 请查看 [BotConfiguration.inheritCoroutineContext]
  *
  * @see Contact 联系人
  * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
@@ -278,6 +276,13 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
     abstract val network: BotNetworkHandler
 }
 
+/**
+ * 获取 [Job] 的协程 [Job]. 此 [Job] 为一个 [SupervisorJob]
+ */
+@get:JvmSynthetic
+inline val Bot.supervisorJob: CompletableJob
+    get() = this.coroutineContext[Job] as CompletableJob
+
 /**
  * 挂起协程直到 [Bot] 下线.
  */
@@ -305,13 +310,13 @@ suspend inline fun Bot.recall(message: MessageChain) =
  * @see recall
  */
 @JvmSynthetic
-inline fun Bot.recallIn(
+inline fun CoroutineScope.recallIn(
     source: MessageSource,
     millis: Long,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
-    kotlinx.coroutines.delay(millis)
-    recall(source)
+    delay(millis)
+    source.recall()
 }
 
 /**
@@ -322,13 +327,13 @@ inline fun Bot.recallIn(
  * @see recall
  */
 @JvmSynthetic
-inline fun Bot.recallIn(
+inline fun CoroutineScope.recallIn(
     message: MessageChain,
     millis: Long,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
-    kotlinx.coroutines.delay(millis)
-    recall(message)
+    delay(millis)
+    message.recall()
 }
 
 /**
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
index e470147d3..6747ea7b7 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
@@ -91,11 +91,15 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
     @Suppress("unused")
     private val offlineListener: Listener<BotOfflineEvent> =
         this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
-            if (event.bot != this.bot) {
+            if (event.bot != this@BotImpl) {
+                return@subscribeAlways
+            }
+            if (!::_network.isInitialized) {
+                // bot 还未登录就被 close
                 return@subscribeAlways
             }
             if (network.areYouOk() && event !is BotOfflineEvent.Force) {
-                // avoid concurrent re-login tasks
+                // network 运行正常
                 return@subscribeAlways
             }
             when (event) {
@@ -262,14 +266,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
         this.launch {
             BotOfflineEvent.Active(this@BotImpl, cause).broadcast()
         }
+        logger.info { "Bot cancelled" + cause?.message?.let { ": $it" }.orEmpty() }
         if (cause == null) {
-            this.cancel()
+            supervisorJob.cancel()
         } else {
-            this.cancel(CancellationException("bot cancelled", cause))
+            supervisorJob.cancel(CancellationException("Bot closed", cause))
         }
     }
 }
 
-
 @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
 internal annotation class ThisApiMustBeUsedInWithConnectionLockBlock
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
index a34bb4736..57e84569a 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
@@ -51,7 +51,7 @@ class EventCancelledException : RuntimeException {
 /**
  * [Bot] 登录完成, 好友列表, 群组列表初始化完成
  */
-data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent, AbstractEvent()
+data class BotOnlineEvent internal constructor(override val bot: Bot) : BotActiveEvent, AbstractEvent()
 
 /**
  * [Bot] 离线.
@@ -59,31 +59,33 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent, AbstractEvent
 sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
 
     /**
-     * 主动离线
+     * 主动离线. 主动广播这个事件也可以让 [Bot] 关闭.
      */
     data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), BotActiveEvent
 
     /**
      * 被挤下线
      */
-    data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet,
+    data class Force internal constructor(override val bot: Bot, val title: String, val message: String) :
+        BotOfflineEvent(), Packet,
         BotPassiveEvent
 
     /**
      * 被服务器断开或因网络问题而掉线
      */
-    data class Dropped(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet, BotPassiveEvent
+    data class Dropped internal constructor(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet,
+        BotPassiveEvent
 
     /**
      * 服务器主动要求更换另一个服务器
      */
-    data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
+    data class RequireReconnect internal constructor(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
 }
 
 /**
  * [Bot] 主动或被动重新登录.
  */
-data class BotReloginEvent(
+data class BotReloginEvent internal constructor(
     override val bot: Bot,
     val cause: Throwable?
 ) : BotEvent, BotActiveEvent, AbstractEvent()
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaFriendly.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaFriendly.kt
index 3d5a3ce4f..3488d7d18 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaFriendly.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaFriendly.kt
@@ -13,15 +13,18 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
 
 /**
  * 表明这个 API 是为了让 Java 使用者调用更方便.
+ *
  * 一般有一定的性能损失, 且不能在 JVM/Android 以外平台使用. 不要在 Kotlin 调用它.
  */
 @MiraiInternalAPI
 @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
 @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
-annotation class JavaFriendlyAPI
+internal annotation class JavaFriendlyAPI
 
 /**
  * [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
+ *
+ * **注意**: 不应该把这个类作为一个类型, 只应使用其中的方法
  */
 @MiraiInternalAPI
 @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused")
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt
index d21d779f2..89597d4ca 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt
@@ -30,6 +30,10 @@ annotation class LowLevelAPI
 
 /**
  * [Bot] 相关协议层低级 API.
+ *
+ * **注意**: 不应该把这个类作为一个类型, 只应使用其中的方法
+ *
+ * **警告**: 所有的低级 API 都可能在任意时刻不经过任何警告和迭代就被修改. 因此非常不建议在任何情况下使用这些 API.
  */
 @MiraiExperimentalAPI
 @Suppress("FunctionName", "unused")
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
index a6a310bea..02e43c8eb 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt
@@ -331,19 +331,15 @@ inline fun MessageSource.isAboutFriend(): Boolean {
  * 引用这条消息
  * @see QuoteReply
  */
-fun MessageSource.quote(): QuoteReply {
-
-    return QuoteReply(this)
-}
+@JvmSynthetic
+inline fun MessageSource.quote(): QuoteReply = QuoteReply(this)
 
 /**
  * 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用.
  * @see QuoteReply
  */
-fun MessageChain.quote(): QuoteReply {
-
-    return QuoteReply(this.source)
-}
+@JvmSynthetic
+inline fun MessageChain.quote(): QuoteReply = QuoteReply(this.source)
 
 /**
  * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
index 1499edf8f..bb7356549 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
@@ -27,7 +27,7 @@ sealed class LoginFailedException constructor(
 ) : RuntimeException(message, cause)
 
 /**
- * 密码输入错误
+ * 密码输入错误 (有时候也会是其他错误, 如 `"当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。"`)
  */
 class WrongPasswordException(message: String?) : LoginFailedException(true, message)
 
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
index bc964c2d3..16104b1e8 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt
@@ -11,6 +11,7 @@
 package net.mamoe.mirai.utils
 
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.network.BotNetworkHandler
 import kotlin.coroutines.CoroutineContext
@@ -34,7 +35,7 @@ open class BotConfiguration {
     /** 设备信息覆盖. 默认使用随机的设备信息. */
     var deviceInfo: ((Context) -> DeviceInfo)? = null
 
-    /** 父 [CoroutineContext]. [Bot] 创建后会覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
+    /** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
     var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
 
     /** 心跳周期. 过长会导致被服务器断开连接. */
@@ -115,21 +116,64 @@ open class BotConfiguration {
     }
 
     /**
-     * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext]
+     * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
+     *
+     * Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
      *
      * 用例:
      * ```
      * coroutineScope {
-     *   val bot = Bot(...)
+     *   val bot = Bot(...) {
+     *     inheritCoroutineContext()
+     *   }
      *   bot.login()
      * } // coroutineScope 会等待 Bot 退出
      * ```
+     *
+     *
+     * **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
+     * ```
+     * coroutineScope { // this: CoroutineScope
+     *   launch {
+     *     while(isActive) {
+     *       delay(500)
+     *       println("I'm alive")
+     *     }
+     *   }
+     *
+     *   val bot = Bot(...) {
+     *      inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
+     *   }
+     *   bot.login()
+     *   bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
+     * }
+     * ```
+     *
+     * 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
+     * ```
+     * suspend fun main() {
+     *   val bot = Bot() {
+     *     inheritCoroutineContext()
+     *   }
+     *   bot.subscribe { ... }
+     *
+     *   // 主线程不会退出, 直到 Bot 离线.
+     * }
+     * ```
+     *
+     * 简言之,
+     * - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
+     * - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
+     *
+     * @see parentCoroutineContext
      */
     @ConfigurationDsl
     suspend inline fun inheritCoroutineContext() {
         parentCoroutineContext = coroutineContext
     }
 
+    /** 标注一个配置 DSL 函数 */
+    @Target(AnnotationTarget.FUNCTION)
     @DslMarker
     annotation class ConfigurationDsl