chore: initial commit

This commit is contained in:
2026-10-22 17:07:08 +08:00
commit 0acef64f48
200 changed files with 35546 additions and 0 deletions
+626
View File
@@ -0,0 +1,626 @@
import { createBot } from "@wp2508/mineflayer";
import type { Bot, BotEvents } from "@wp2508/mineflayer";
import {bot_instance_config, bot_status} from "../../types/bot/instance/bot_runtime.ts";
import {
validate_host,
validate_port,
validate_proxy,
validate_username,
validate_version
} from "../../utils/validate.ts";
import {type bot_operation_code, type validate_code, validate_code_map} from "../../utils/validate_code_map.ts";
import { EventEmitter } from "node:events";
import { Mutex } from "async-mutex";
import {Logger} from "../../utils/logger.ts";
import {get_project_root_path} from "../../utils/path_utils.ts";
import * as path from 'path';
import {log} from "../../types/utils/logger.ts";
import {MinecraftJsonParser} from "../../utils/message_json_parser.ts";
import {remember_command_tree} from '../../utils/command_completion.ts';
import {create_info_manager, type InfoManager, type InfoUpdate} from '../info/init.ts';
import {create_function_manager, type FunctionManager} from '../function/init.ts';
import {PluginManager} from '../plugins/init.ts';
import type {WorkflowManager, WorkflowRunEvent} from '../../types/bot/workflow/runtime.ts';
// 深度清理的最大递归深度与最大节点数,避免遍历无关的深层对象图
const CLEANUP_MAX_DEPTH = 8;
const CLEANUP_MAX_VISITED = 2000;
// 共享 / 全局对象,禁止递归进入,避免误清理缓存或全局状态
const CLEANUP_SKIP_KEYS = new Set(['registry', 'supportFeature', 'constructor']);
// 需要显式清理的定时器与待发队列字段名
const CLEANUP_TIMER_KEYS = ['closeTimer', '_keepAliveTimer', 'keepAliveTimer', 'doPhysicsTimer'];
const CLEANUP_QUEUE_KEYS = ['_writeQueue', '_priorityWriteQueue', '_mcBundle'];
// 全局对象,绝不能被遍历清理
const CLEANUP_PROTECTED: unknown[] = [globalThis, process, console];
// 'end' 事件未按时派发时的兜底清理延时(毫秒)
const CLEANUP_FALLBACK_DELAY = 1000;
// 事件表:mineflayer 的全部事件 + 实例自有的 log 事件
export interface bot_instance_event extends BotEvents {
log: (log: log[]) => void;
status_update: (status: bot_status) => void;
instance_created: (instance: Bot) => void;
info_update: (event: InfoUpdate) => void;
connection_lost: () => void;
connection_requested: () => void;
reconnect_attempt: () => void;
workflow_update: (event: WorkflowRunEvent) => void;
}
export type bot_event_name = keyof bot_instance_event;
// 单个事件的回调参数元组,例如 chat -> [username, message, translate, jsonMsg, matches]
export type bot_event_args<E extends bot_event_name> = Parameters<bot_instance_event[E]>;
export type bot_event_handler<E extends bot_event_name> = (...args: bot_event_args<E>) => void;
// onAny / onceAny 在事件名之后接收对应事件的全部参数;
// 事件名受联合类型约束,参数保持宽松以便统一处理任意事件
export type bot_event_any_handler = (event: bot_event_name, ...args: any[]) => void;
export type bot_instance_context<T> = {
readonly bot_instance: Bot | null,
readonly info_manager: InfoManager | null,
readonly function_manager: FunctionManager | null,
workflow_manager: WorkflowManager | null,
bot_config: bot_instance_config,
readonly bot_status: bot_status,
readonly plugin_manager: PluginManager,
event_emitter: EventEmitter,
logger: Logger | null,
action_lock: boolean,
instance_lock: Mutex,
validate_config: (config: bot_instance_config) => validate_code,
log: (log: log[]) => void,
on: <E extends bot_event_name>(event: E, callback: bot_event_handler<E>) => void,
onAny: (callback: bot_event_any_handler) => void,
once: <E extends bot_event_name>(event: E, callback: bot_event_handler<E>) => void,
onceAny: (callback: bot_event_any_handler) => void,
off: <E extends bot_event_name>(event: E, callback: bot_event_handler<E>) => void,
emit: <E extends bot_event_name>(event: E, ...args: bot_event_args<E>) => void,
connect: () => Promise<bot_operation_code>,
create_instance: () => Promise<bot_operation_code>,
disconnect: () => Promise<bot_operation_code>,
schedule_reconnect: (delay: number) => Promise<void>,
cancel_reconnect: () => void,
inject: (callback: (context: bot_instance_context<T>) => void) => void,
clear_this_context: () => Promise<void>
}
export class BotContext implements bot_instance_context<any> {
get is_disposed(): boolean {return this.disposal_requested;}
public workflow_manager: WorkflowManager | null = null;
private bot_instance_origin: Bot | null = null;
public get bot_instance(): Bot | null {
return this.bot_instance_origin;
}
private info_manager_origin: InfoManager | null = null;
public get info_manager(): InfoManager | null {
return this.info_manager_origin;
}
private info_unsubscribe: (() => void) | undefined;
private function_manager_origin: FunctionManager | null = null;
public get function_manager(): FunctionManager | null {
return this.function_manager_origin;
}
private cleanup_task: Promise<void> | undefined;
private dispose_task: Promise<void> | undefined;
private disposal_requested = false;
private operation_sequence = 0;
private reconnect_timer: ReturnType<typeof setTimeout> | undefined;
public readonly bot_config: bot_instance_config = {
username: '',
host: '',
port: 0,
version: '',
user_id: 0,
bot_id: 0,
proxy: null
};
private bot_status_origin: bot_status = 'offline';
public get bot_status(): bot_status {
return this.bot_status_origin;
}
private set_status(value: bot_status) {
if (this.bot_status_origin === value) {
return;
}
const transitions: Record<bot_status, readonly bot_status[]> = {
offline: ['ready', 'reconnecting'],
ready: ['connecting', 'disconnecting'],
connecting: ['online', 'disconnecting'],
online: ['disconnecting'],
disconnecting: ['offline'],
reconnecting: ['ready', 'offline'],
};
if (!transitions[this.bot_status_origin].includes(value)) {
throw new Error(`无效的机器人状态转换: ${this.bot_status_origin} -> ${value}`);
}
this.bot_status_origin = value;
this.notify('status_update', value);
}
private notify<E extends bot_event_name>(event: E, ...args: bot_event_args<E>) {
try {
this.emit(event, ...args);
} catch (error) {
this.logger?.log([this.logger.error(`生命周期事件 ${String(event)} 处理失败: ${String(error)}`)]);
}
}
public readonly plugin_manager: PluginManager;
public event_emitter = new EventEmitter()
// 记录每个原始回调对应的包装函数,off 时才能找回并移除同一个监听器
private readonly event_wrappers = new Map<
string,
Map<(...args: any[]) => void, ((...args: any[]) => void)[]>
>();
public logger: Logger | null = null;
public action_lock: boolean = false;
// 串行化 create_instance / connect / disconnect,防止并发操作实例
public readonly instance_lock : Mutex = new Mutex();
constructor(config: bot_instance_config) {
this.bot_config = config;
const log_path = path.join(get_project_root_path(), 'data/bot/', this.bot_config.bot_id.toString(), '/log');
this.logger = new Logger(log_path);
this.plugin_manager = new PluginManager(this);
}
public log(log: log[]) {
this.logger.log(log);
this.emit('log', log);
}
public create_instance(): Promise<bot_operation_code> {
if (this.bot_status === 'reconnecting' || this.reconnect_timer !== undefined) this.cancel_reconnect();
const sequence = this.operation_sequence;
return this.instance_lock.runExclusive(async () => {
await this.cleanup_task;
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
if (this.bot_status === 'online') return validate_code_map.bot_context.instance_connected;
if (this.bot_status === 'connecting') return validate_code_map.bot_context.instance_connecting;
if (this.bot_instance) return validate_code_map.bot_context.ok;
const result = await this.create_bot_instance(sequence);
if (result !== validate_code_map.bot_context.ok) await this.cleanup_task;
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
return result;
});
}
private async create_bot_instance(sequence: number): Promise<bot_operation_code> {
const result = this.validate_config(this.bot_config);
if (result !== validate_code_map.username.ok) return result;
let instance: Bot | undefined;
let pending_manager: InfoManager | undefined;
let pending_functions: FunctionManager | undefined;
let initialized = false;
const failure = (): bot_operation_code | undefined => {
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
if (this.bot_instance !== instance) return validate_code_map.bot_context.error;
};
try {
const {port, plugins, workflow, config_revision, ...options} = this.bot_config;
instance = createBot({...options, ...(port != null ? {port} : {}), autoConnect: false, hideErrors: true});
(instance as Bot & {server_address: string}).server_address = `${options.host.includes(':') ? `[${options.host}]` : options.host}:${port ?? 25565}`;
this.bot_instance_origin = instance;
instance._client.on('declare_commands', packet => remember_command_tree(instance, packet));
const terminate = <E extends 'end' | 'kicked' | 'error'>(event: E, ...args: bot_event_args<E>) => {
if (this.bot_instance !== instance || this.disposal_requested) return;
const reason = args[0];
if (event === 'error') {
this.log([this.logger.error(`连接错误: ${String(reason)}`)]);
} else if (event === 'kicked') {
let kickReason: Parameters<typeof MinecraftJsonParser.toLog>[0] = args[0] as Parameters<typeof MinecraftJsonParser.toLog>[0];
if (typeof kickReason === 'string') {
try { kickReason = JSON.parse(kickReason); }
catch { /* Plain-text disconnect reasons are valid too. */ }
}
const reasonLog = MinecraftJsonParser.toLog(kickReason);
if (!reasonLog.length) {
let fallback = '未知原因';
try { fallback = JSON.stringify(kickReason) ?? fallback; }
catch { /* Keep a readable fallback for malformed data. */ }
reasonLog.push(this.logger.error(fallback));
}
this.log([
this.logger.custom('[被踢出] ', '#cf8e8e', ['bold']),
...reasonLog,
]);
} else {
this.log([this.logger.warning(`连接已断开: ${String(reason ?? '未知原因')}`)]);
}
const sequence = this.operation_sequence;
// Initialization failures are handled by the caller, including retry attempts.
const report_connection_lost = initialized;
const cleanup = this.clear_bot_instance(event === 'end');
this.notify(event, ...args);
void cleanup.then(() => {
if (report_connection_lost && !this.disposal_requested && sequence === this.operation_sequence) {
this.notify('connection_lost');
}
}).catch(error => {
this.logger?.log([this.logger.error(`清理实例失败: ${String(error)}`)]);
});
};
instance.on('end', reason => terminate('end', reason));
instance.on('kicked', (reason, logged_in) => terminate('kicked', reason, logged_in));
instance.on('error', (error: Error) => terminate('error', error));
instance.on('initialLogin', () => {
if (this.bot_instance !== instance || this.bot_status !== 'connecting' || this.disposal_requested) return;
this.set_status('online');
});
instance.on("message", (jsonMsg, position) => {
const channel = {
chat: {label: '聊天', color: '#8fb0c9'},
system: {label: '系统', color: '#d1b478'},
game_info: {label: '提示', color: '#8fb98f'},
}[position] ?? {label: String(position), color: '#8fb0c9'};
this.log([
this.logger.custom(`[${channel.label}] `, channel.color, ['bold']),
...MinecraftJsonParser.parse(jsonMsg).log,
]);
})
instance.onAny((event: string, ...args: any[]) => {
if (this.bot_instance === instance && !this.disposal_requested) this.emit(event, ...args);
});
this.set_status('ready');
const before_loading = failure();
if (before_loading !== undefined) return before_loading;
const manager = pending_manager = await create_info_manager(instance, {
bot_id: this.bot_config.bot_id, plugin_source: this.plugin_manager,
workflow_source: this.workflow_manager,
});
const after_loading = failure();
if (after_loading !== undefined) return after_loading;
this.info_manager_origin = manager;
pending_manager = undefined;
this.info_unsubscribe = manager.on_update(event => {
if (this.bot_instance === instance && this.info_manager === manager
&& !this.disposal_requested) {
this.notify('info_update', event);
}
});
pending_functions = await create_function_manager(this);
const after_functions = failure();
if (after_functions !== undefined) return after_functions;
this.function_manager_origin = pending_functions;
pending_functions = undefined;
this.notify('instance_created', instance);
const after_notification = failure();
if (after_notification !== undefined) return after_notification;
this.log([this.logger.custom('[NODE] 创建机器人实例成功', '#8fb98f', ['bold'])]);
const after_log = failure();
if (after_log !== undefined) return after_log;
initialized = true;
return validate_code_map.bot_context.ok;
} catch (error) {
this.logger?.log([this.logger.error(`创建实例失败: ${String(error)}`)]);
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
return validate_code_map.bot_context.error;
} finally {
if (!initialized) {
pending_functions?.dispose();
try { pending_manager?.dispose(); }
catch (error) { this.logger?.log([this.logger.error(`清理信息管理器失败: ${String(error)}`)]); }
if (instance && this.bot_instance === instance) await this.clear_bot_instance();
else await this.cleanup_task;
}
}
}
public inject(callback: (context: bot_instance_context<any>) => void) {
callback(this as bot_instance_context<any>);
}
public connect(): Promise<bot_operation_code> {
if (this.bot_status === 'reconnecting' || this.reconnect_timer !== undefined) this.cancel_reconnect();
const sequence = this.operation_sequence;
if (!this.disposal_requested) this.notify('connection_requested');
return this.connect_instance(sequence);
}
private connect_instance(sequence: number): Promise<bot_operation_code> {
return this.instance_lock.runExclusive(async () => {
await this.cleanup_task;
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
if (this.bot_status === 'online') return validate_code_map.bot_context.instance_connected;
if (this.bot_status === 'connecting') return validate_code_map.bot_context.instance_connecting;
if (this.bot_status === 'reconnecting') this.set_status('offline');
if (!this.bot_instance) {
const result = await this.create_bot_instance(sequence);
if (result !== validate_code_map.bot_context.ok) {
await this.cleanup_task;
return result;
}
}
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
if (sequence !== this.operation_sequence) return validate_code_map.bot_context.cancelled;
const instance = this.bot_instance;
if (!instance) return validate_code_map.bot_context.error;
this.set_status('connecting');
if (this.disposal_requested || sequence !== this.operation_sequence) {
await this.clear_bot_instance();
return validate_code_map.bot_context.cancelled;
}
try {
instance.connect();
if (this.bot_instance !== instance) {
await this.cleanup_task;
return validate_code_map.bot_context.error;
}
return validate_code_map.bot_context.ok;
} catch (error) {
this.logger?.log([this.logger.error(`连接实例失败: ${String(error)}`)]);
await this.clear_bot_instance();
return validate_code_map.bot_context.error;
}
});
}
public disconnect(): Promise<bot_operation_code> {
this.cancel_reconnect();
return this.instance_lock.runExclusive(async () => {
if (this.disposal_requested) return validate_code_map.bot_context.disposed;
await this.clear_bot_instance();
return validate_code_map.bot_context.ok;
});
}
public cancel_reconnect(): void {
this.operation_sequence++;
clearTimeout(this.reconnect_timer);
this.reconnect_timer = undefined;
if (this.bot_status === 'reconnecting') this.set_status('offline');
}
public async schedule_reconnect(delay: number): Promise<void> {
if (!Number.isInteger(delay) || delay < 0 || delay > 2_147_483_647) {
throw new Error('重连延时必须是有效的毫秒整数');
}
const sequence = this.operation_sequence;
await this.instance_lock.runExclusive(() => {
if (this.disposal_requested || sequence !== this.operation_sequence || this.bot_status !== 'offline') return;
this.reconnect_timer = setTimeout(() => {
this.reconnect_timer = undefined;
if (this.disposal_requested || sequence !== this.operation_sequence) return;
this.notify('reconnect_attempt');
void this.connect_instance(sequence).then(result => {
if (result !== validate_code_map.bot_context.ok && !this.disposal_requested
&& sequence === this.operation_sequence && this.bot_status === 'offline') {
this.notify('connection_lost');
}
}).catch(error => {
this.logger?.log([this.logger.error(`自动重连失败: ${String(error)}`)]);
});
}, delay);
this.set_status('reconnecting');
});
}
public on<E extends bot_event_name>(event: E, callback: bot_event_handler<E>) {
const wrapper: bot_event_handler<E> = (...args) => callback(...args);
this.remember_wrapper(event, callback, wrapper);
this.event_emitter.on(event, wrapper);
}
public onAny(callback: bot_event_any_handler) {
const wrapper = (event: string, ...args: any[]) => callback(event as bot_event_name, ...args);
this.remember_wrapper('any', callback, wrapper);
this.event_emitter.on('any', wrapper);
}
public once<E extends bot_event_name>(event: E, callback: bot_event_handler<E>) {
const wrapper: bot_event_handler<E> = (...args) => callback(...args);
this.remember_wrapper(event, callback, wrapper);
this.event_emitter.once(event, wrapper);
}
public onceAny(callback: bot_event_any_handler) {
const wrapper = (event: string, ...args: any[]) => callback(event as bot_event_name, ...args);
this.remember_wrapper('any', callback, wrapper);
this.event_emitter.once('any', wrapper);
}
public off<E extends bot_event_name>(event: E, callback: bot_event_handler<E>) {
const callbacks = this.event_wrappers.get(event);
const wrappers = callbacks?.get(callback);
if (!callbacks || !wrappers) return;
const wrapper = wrappers.pop();
if (!wrapper) return;
this.event_emitter.off(event, wrapper);
if (wrappers.length === 0) {
callbacks.delete(callback);
}
}
// 记录原始回调 -> 包装函数栈,off 时才能找回并移除同一个监听器;
// 同一回调可多次注册,off 从栈顶移除最近一次
private remember_wrapper(
event: string,
callback: (...args: any[]) => void,
wrapper: (...args: any[]) => void,
) {
let callbacks = this.event_wrappers.get(event);
if (!callbacks) {
callbacks = new Map();
this.event_wrappers.set(event, callbacks);
}
let wrappers = callbacks.get(callback);
if (!wrappers) {
wrappers = [];
callbacks.set(callback, wrappers);
}
wrappers.push(wrapper);
}
public emit<E extends bot_event_name>(event: E, ...args: bot_event_args<E>): void;
public emit(event: string, ...args: any[]): void;
public emit(event: string, ...args: any[]) {
// 'error' 无监听器时 EventEmitter 会抛出;转发前先兜底,避免 bot 的错误
// 变成未捕获异常,同时保证 'any' / onAny 仍能观察到该事件
if (event !== 'error' || this.event_emitter.listenerCount('error') > 0) {
this.event_emitter.emit(event, ...args);
}
this.event_emitter.emit('any', event, ...args);
}
private clear_bot_instance(ended = false): Promise<void> {
if (this.cleanup_task) return this.cleanup_task;
const instance = this.bot_instance;
if (!instance) return Promise.resolve();
this.bot_instance_origin = null;
const manager = this.info_manager_origin;
const functions = this.function_manager_origin;
this.function_manager_origin = null;
const unsubscribe = this.info_unsubscribe;
this.info_manager_origin = null;
this.info_unsubscribe = undefined;
let cleaned = false;
let finish!: () => void;
const task = new Promise<void>(resolve => { finish = resolve; });
this.cleanup_task = task.then(() => {
this.cleanup_task = undefined;
if (!this.disposal_requested) this.set_status('offline');
});
const cleanup_task = this.cleanup_task;
const run_cleanup = () => {
if (cleaned) return;
cleaned = true;
clearTimeout(fallback_timer);
try {
this.deep_cleanup(instance, new Set(), 0);
this.log([this.logger.custom('[NODE] 机器人实例已关闭', '#cf8e8e', ['bold'])]);
} catch (error) {
this.logger?.log([this.logger.error(`清理实例资源失败: ${String(error)}`)]);
} finally {
finish();
}
};
// 等 end 的所有监听器执行完毕后再摘除监听器,保留 mineflayer 的内部资源清理。
const on_end = () => { queueMicrotask(run_cleanup); };
const fallback_timer = setTimeout(run_cleanup, CLEANUP_FALLBACK_DELAY);
for (const cleanup of [unsubscribe, manager ? () => manager.dispose() : undefined, functions ? () => functions.dispose() : undefined]) {
try { cleanup?.(); }
catch (error) { this.logger?.log([this.logger.error(`清理信息管理器失败: ${String(error)}`)]); }
}
this.set_status('disconnecting');
if (ended) {
on_end();
} else {
instance.once('end', on_end);
try {
instance.end(validate_code_map.bot_context.instance_end_by_user.toString());
} catch {
on_end();
}
}
return cleanup_task;
}
// 深度遍历对象图,释放监听器 / 定时器 / 流句柄等残留状态;
// seen 去重,depth 限制递归深度,visited 限制遍历规模,避免开销失控
private deep_cleanup(target: unknown, seen: Set<object>, depth: number, visited = { count: 0 }) {
if (target === null || typeof target !== 'object') return;
if (depth > CLEANUP_MAX_DEPTH || visited.count > CLEANUP_MAX_VISITED) return;
const object = target as Record<string, any>;
if (CLEANUP_PROTECTED.includes(object)) return;
if (seen.has(object)) return;
seen.add(object);
visited.count++;
this.release_own_state(object);
for (const key of Object.keys(object)) {
if (CLEANUP_SKIP_KEYS.has(key)) continue;
let value: unknown;
try {
value = object[key];
} catch {
continue;
}
if (value === null || typeof value !== 'object') continue;
this.deep_cleanup(value, seen, depth + 1, visited);
}
}
// 释放单个对象自身持有的监听器 / 定时器 / 流句柄 / 待发队列
private release_own_state(object: Record<string, any>) {
for (const key of CLEANUP_TIMER_KEYS) {
const timer = object[key];
if (timer === null || timer === undefined) continue;
clearInterval(timer as NodeJS.Timeout);
clearTimeout(timer as NodeJS.Timeout);
try {
object[key] = null;
} catch {
// 只读 / 冻结对象无法赋值,忽略
}
}
for (const key of CLEANUP_QUEUE_KEYS) {
if (Array.isArray(object[key])) {
(object[key] as unknown[]).length = 0;
}
}
// 仅销毁流 / socket 类句柄,避免误调用无关对象的 destroy
if (typeof object.destroy === 'function' && object.destroyed !== true
&& (typeof object.pipe === 'function' || typeof object.write === 'function')) {
try {
object.destroy();
} catch {
// 忽略已关闭句柄在销毁时的异常
}
}
if (typeof object.removeAllListeners === 'function') {
object.removeAllListeners();
}
}
public validate_config(config: bot_instance_config): validate_code {
const username_validate = validate_username(config.username);
if (username_validate !== validate_code_map.username.ok) return username_validate;
const host_validate = validate_host(config.host);
if (host_validate !== validate_code_map.host.ok) return host_validate;
if (config.port != null) {
const port_validate = validate_port(config.port);
if (port_validate !== validate_code_map.port.ok) return port_validate;
}
const version_validate = validate_version(config.version);
if (version_validate !== validate_code_map.version.ok) return version_validate;
if (config.proxy != null) {
const proxy_validate = validate_proxy(config.proxy);
if (proxy_validate !== validate_code_map.proxy.ok) return proxy_validate;
}
return validate_code_map.username.ok;
}
public clear_this_context(): Promise<void> {
if (this.dispose_task) return this.dispose_task;
this.disposal_requested = true;
this.cancel_reconnect();
const plugins_disposed = this.plugin_manager.dispose().catch(error => {
this.logger?.log([this.logger.error(`插件卸载失败: ${String(error)}`)]);
});
const workflow_disposed = this.workflow_manager?.dispose().catch(error => {
this.logger?.log([this.logger.error(`Workflow cleanup failed: ${String(error)}`)]);
});
this.dispose_task = Promise.all([plugins_disposed, workflow_disposed]).then(() => this.instance_lock.runExclusive(async () => {
await this.clear_bot_instance();
this.action_lock = false;
if (this.bot_status === 'offline') {
this.notify('status_update', 'offline');
} else {
this.set_status('offline');
}
this.event_emitter.removeAllListeners();
this.event_wrappers.clear();
this.workflow_manager = null;
await this.logger?.close();
}));
return this.dispose_task;
}
}