@dcrays/dcgchat-test 0.2.26 → 0.2.28

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.
package/src/tool.ts CHANGED
@@ -1,140 +1,136 @@
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
+ import { getMsgParams, getMsgStatus, getWsConnection } from './utils/global.js'
3
+ import { dcgLogger } from './utils/log.js'
4
+ import { isWsOpen } from './transport.js'
1
5
 
2
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
- import { getWsConnection } from "./connection.js";
4
-
5
- let msgParams = {} as {
6
- userId: number;
7
- token: string;
8
- sessionId: string;
9
- messageId: string;
10
- domainId: string;
11
- appId: string;
12
- botId: string;
13
- agentId: string;
14
- }
15
- let msgStatus: 'running' | 'finished' | '' = '';
16
- export function setMsgParams(params: any) {
17
- msgParams = params;
18
- }
19
-
20
- export function getMsgParams() {
21
- return msgParams;
22
- }
23
-
24
- export function setMsgStatus(status: 'running' | 'finished' | '') {
25
- msgStatus = status;
26
- }
27
-
28
- export function getMsgStatus() {
29
- return msgStatus;
30
- }
31
- let toolCallId = '';
32
- let toolName = '';
33
- type PluginHookName = "before_model_resolve" | "before_prompt_build" | "before_agent_start" | "llm_input" | "llm_output" | "agent_end" | "before_compaction" | "after_compaction" | "before_reset" | "message_received" | "message_sending" | "message_sent" | "before_tool_call" | "after_tool_call" | "tool_result_persist" | "before_message_write" | "session_start" | "session_end" | "subagent_spawning" | "subagent_delivery_target" | "subagent_spawned" | "subagent_ended" | "gateway_start" | "gateway_stop";
6
+ let toolCallId = ''
7
+ let toolName = ''
8
+ type PluginHookName =
9
+ | 'before_model_resolve'
10
+ | 'before_prompt_build'
11
+ | 'before_agent_start'
12
+ | 'llm_input'
13
+ | 'llm_output'
14
+ | 'agent_end'
15
+ | 'before_compaction'
16
+ | 'after_compaction'
17
+ | 'before_reset'
18
+ | 'message_received'
19
+ | 'message_sending'
20
+ | 'message_sent'
21
+ | 'before_tool_call'
22
+ | 'after_tool_call'
23
+ | 'tool_result_persist'
24
+ | 'before_message_write'
25
+ | 'session_start'
26
+ | 'session_end'
27
+ | 'subagent_spawning'
28
+ | 'subagent_delivery_target'
29
+ | 'subagent_spawned'
30
+ | 'subagent_ended'
31
+ | 'gateway_start'
32
+ | 'gateway_stop'
34
33
  const eventList = [
35
- {event: 'message_received', message: ''},
34
+ { event: 'message_received', message: '' },
36
35
  // {event: 'before_model_resolve', message: ''},
37
36
  // {event: 'before_prompt_build', message: '正在查阅背景资料,构建思考逻辑'},
38
37
  // {event: 'before_agent_start', message: '书灵墨宝已就位,准备开始执行任务'},
39
- {event: 'subagent_spawning', message: ''},
40
- {event: 'subagent_spawned', message: ''},
41
- {event: 'subagent_delivery_target', message: ''},
38
+ { event: 'subagent_spawning', message: '' },
39
+ { event: 'subagent_spawned', message: '' },
40
+ { event: 'subagent_delivery_target', message: '' },
42
41
  // {event: 'llm_input', message: ''},
43
- {event: 'llm_output', message: ''},
42
+ { event: 'llm_output', message: '' },
44
43
  // {event: 'agent_end', message: '核心任务已处理完毕...'},
45
- {event: 'subagent_ended', message: ''},
44
+ { event: 'subagent_ended', message: '' },
46
45
  // {event: 'before_message_write', message: '正在将本次对话存入记忆库...'},
47
46
  // {event: 'message_sending', message: ''},
48
47
  // {event: 'message_send', message: ''},
49
- {event: 'before_tool_call', message: ''},
50
- {event: 'after_tool_call', message: ''},
51
- ];
48
+ { event: 'before_tool_call', message: '' },
49
+ { event: 'after_tool_call', message: '' }
50
+ ]
52
51
 
53
52
  function sendToolCallMessage(text: string, toolCallId: string, isCover: number) {
54
53
  const ws = getWsConnection()
55
- const params = getMsgParams();
56
- const status = getMsgStatus();
57
- if (ws?.readyState === WebSocket.OPEN && status === 'running') {
58
- ws.send(JSON.stringify({
59
- messageType: "openclaw_bot_chat",
60
- _userId: params?.userId,
61
- source: "client",
62
- content: {
63
- bot_token: params?.token,
64
- domain_id: params?.domainId,
65
- app_id: params?.appId,
66
- bot_id: params?.botId,
67
- agent_id: params?.agentId,
68
- response: 'all_finished',
69
- session_id:params?.sessionId,
70
- message_id: params?.messageId || Date.now().toString()
71
- },
72
- }))
73
- ws.send(JSON.stringify({
74
- messageType: "openclaw_bot_chat",
75
- _userId: params?.userId,
76
- source: "client",
77
- content: {
78
- bot_token: params?.token,
79
- domain_id: params?.domainId,
80
- app_id: params?.appId,
81
- bot_id: params?.botId,
82
- agent_id: params?.agentId,
83
- tool_call_id: toolCallId,
84
- is_cover: isCover,
85
- thinking_content: text,
86
- response: '',
87
- session_id:params?.sessionId,
88
- message_id: params?.messageId || Date.now().toString()
89
- },
90
- }));
91
- ws.send(JSON.stringify({
92
- messageType: "openclaw_bot_chat",
93
- _userId: params?.userId,
94
- source: "client",
95
- content: {
96
- bot_token: params?.token,
97
- domain_id: params?.domainId,
98
- app_id: params?.appId,
99
- bot_id: params?.botId,
100
- agent_id: params?.agentId,
101
- response: 'all_finished',
102
- session_id:params?.sessionId,
103
- message_id: params?.messageId || Date.now().toString()
104
- },
105
- }))
54
+ const params = getMsgParams()
55
+ if (isWsOpen()) {
56
+ ws?.send(
57
+ JSON.stringify({
58
+ messageType: 'openclaw_bot_chat',
59
+ _userId: params?.userId,
60
+ source: 'client',
61
+ is_finish: -1,
62
+ content: {
63
+ is_finish: -1,
64
+ bot_token: params?.token,
65
+ domain_id: params?.domainId,
66
+ app_id: params?.appId,
67
+ bot_id: params?.botId,
68
+ agent_id: params?.agentId,
69
+ tool_call_id: toolCallId,
70
+ is_cover: isCover,
71
+ thinking_content: text,
72
+ response: '',
73
+ session_id: params?.sessionId,
74
+ message_id: params?.messageId || Date.now().toString()
75
+ }
76
+ })
77
+ )
106
78
  }
107
79
  }
108
80
 
109
81
  export function monitoringToolMessage(api: OpenClawPluginApi) {
110
- for (const item of eventList) {
111
- api.on(item.event as PluginHookName, (event: any) => {
112
- console.log("🚀 ~ 工具调用结果: ~ event:", item.event)
82
+ for (const item of eventList) {
83
+ api.on(item.event as PluginHookName, (event: any) => {
84
+ const status = getMsgStatus()
85
+ if (status === 'running') {
86
+ dcgLogger(`工具调用结果: ~ event:${item.event} ${JSON.stringify(event)}`)
113
87
  if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
114
- const { result: _result, ...rest } = event;
88
+ const { result: _result, ...rest } = event
115
89
  const text = JSON.stringify({
116
90
  type: item.event,
117
91
  specialIdentification: 'dcgchat_tool_call_special_identification',
118
92
  callId: event.toolCallId || event.runId || Date.now().toString(),
119
93
  ...rest,
120
94
  status: item.event === 'after_tool_call' ? 'finished' : 'running'
121
- });
95
+ })
122
96
  sendToolCallMessage(text, event.toolCallId || event.runId || Date.now().toString(), item.event === 'after_tool_call' ? 1 : 0)
123
97
  } else if (item.event) {
98
+ if (item.event === 'llm_output') {
99
+ if (event.lastAssistant?.errorMessage === '429-账户额度耗尽') {
100
+ const ws = getWsConnection()
101
+ const params = getMsgParams()
102
+ if (isWsOpen()) {
103
+ ws?.send(
104
+ JSON.stringify({
105
+ messageType: 'openclaw_bot_cost',
106
+ _userId: params?.userId,
107
+ source: 'client',
108
+ content: {
109
+ bot_token: params?.token,
110
+ domain_id: params?.domainId,
111
+ app_id: params?.appId,
112
+ bot_id: params?.botId,
113
+ agent_id: params?.agentId,
114
+ response: '您的余额不足,请充值后继续使用',
115
+ session_id: params?.sessionId,
116
+ message_id: params?.messageId || Date.now().toString()
117
+ }
118
+ })
119
+ )
120
+ }
121
+ return
122
+ }
123
+ }
124
124
  const text = JSON.stringify({
125
- type: item.event,
126
- specialIdentification: 'dcgchat_tool_call_special_identification',
127
- toolName: '',
128
- callId: event.runId || Date.now().toString(),
129
- params: item.message
130
- });
131
- sendToolCallMessage(text, event.runId || Date.now().toString(), 0)
132
- console.log('消息事件:', item.event, item.message)
133
- }
134
- // log?.(`dcgchat[${params?.sessionId}]:11111111 tool message to ${params?.sessionId}, ${JSON.stringify(rest)}`);
135
- // } else {
136
- // log?.(`dcgchat[${params?.sessionId}]:22222222 tool ${status} message to ${ws?.readyState}, ${JSON.stringify(rest)}`);
137
- // }
138
- });
139
- }
140
- }
125
+ type: item.event,
126
+ specialIdentification: 'dcgchat_tool_call_special_identification',
127
+ toolName: '',
128
+ callId: event.runId || Date.now().toString(),
129
+ params: item.message
130
+ })
131
+ sendToolCallMessage(text, event.runId || Date.now().toString(), 0)
132
+ }
133
+ }
134
+ })
135
+ }
136
+ }
@@ -0,0 +1,108 @@
1
+ import { getWsConnection } from './utils/global.js'
2
+ import { dcgLogger } from './utils/log.js'
3
+
4
+ export type DcgchatMsgContext = {
5
+ userId: number
6
+ botToken: string
7
+ domainId?: string
8
+ appId?: string
9
+ botId?: string
10
+ agentId?: string
11
+ sessionId: string
12
+ messageId: string
13
+ }
14
+
15
+ export function createMsgContext(msg: {
16
+ _userId: number
17
+ content: {
18
+ bot_token: string
19
+ domain_id?: string
20
+ app_id?: string
21
+ bot_id?: string
22
+ agent_id?: string
23
+ session_id: string
24
+ message_id: string
25
+ }
26
+ }): DcgchatMsgContext {
27
+ return {
28
+ userId: msg._userId,
29
+ botToken: msg.content.bot_token,
30
+ domainId: msg.content.domain_id,
31
+ appId: msg.content.app_id,
32
+ botId: msg.content.bot_id,
33
+ agentId: msg.content.agent_id,
34
+ sessionId: msg.content.session_id,
35
+ messageId: msg.content.message_id
36
+ }
37
+ }
38
+
39
+ function buildContent(ctx: DcgchatMsgContext, extra: Record<string, unknown>) {
40
+ return {
41
+ bot_token: ctx.botToken,
42
+ domain_id: ctx.domainId,
43
+ app_id: ctx.appId,
44
+ bot_id: ctx.botId,
45
+ agent_id: ctx.agentId,
46
+ session_id: ctx.sessionId,
47
+ message_id: ctx.messageId || Date.now().toString(),
48
+ ...extra
49
+ }
50
+ }
51
+
52
+ function buildEnvelope(ctx: DcgchatMsgContext, extra: Record<string, unknown>) {
53
+ return {
54
+ messageType: 'openclaw_bot_chat' as const,
55
+ _userId: ctx.userId,
56
+ source: 'client' as const,
57
+ content: buildContent(ctx, extra)
58
+ }
59
+ }
60
+
61
+ export function isWsOpen(): boolean {
62
+ const isOpen = getWsConnection()?.readyState === WebSocket.OPEN
63
+ if (!isOpen) {
64
+ dcgLogger(`socket not ready ${getWsConnection()?.readyState}`, 'error')
65
+ }
66
+ return isOpen
67
+ }
68
+
69
+ /**
70
+ * Content is stringified separately (double-encoded) to match the
71
+ * dcgchat wire protocol used by the chat stream path.
72
+ */
73
+ export function wsSend(ctx: DcgchatMsgContext, extra: Record<string, unknown>): boolean {
74
+ const ws = getWsConnection()
75
+ if (ws?.readyState !== WebSocket.OPEN) return false
76
+ const envelope = buildEnvelope(ctx, extra)
77
+ ws.send(JSON.stringify({ ...envelope, content: JSON.stringify(envelope.content) }))
78
+ return true
79
+ }
80
+
81
+ /**
82
+ * Content stays as a nested object (single-encoded).
83
+ * Matches the legacy wire format used by media and outbound-pipeline messages.
84
+ */
85
+ export function wsSendRaw(ctx: DcgchatMsgContext, extra: Record<string, unknown>): boolean {
86
+ const ws = getWsConnection()
87
+ if (isWsOpen()) {
88
+ ws?.send(JSON.stringify(buildEnvelope(ctx, extra)))
89
+ }
90
+ return true
91
+ }
92
+
93
+ export function sendChunk(ctx: DcgchatMsgContext, text: string): boolean {
94
+ return wsSend(ctx, { response: text, state: 'chunk' })
95
+ }
96
+
97
+ export function sendFinal(ctx: DcgchatMsgContext): boolean {
98
+ dcgLogger(` message handling complete`)
99
+ return wsSend(ctx, { response: '', state: 'final' })
100
+ }
101
+
102
+ export function sendText(ctx: DcgchatMsgContext, text: string): boolean {
103
+ return wsSend(ctx, { response: text })
104
+ }
105
+
106
+ export function sendError(ctx: DcgchatMsgContext, errorMsg: string): boolean {
107
+ return wsSend(ctx, { response: `[错误] ${errorMsg}`, state: 'final' })
108
+ }
package/src/types.ts CHANGED
@@ -2,27 +2,27 @@
2
2
  * 插件配置(channels.dcgchat 下的字段)
3
3
  */
4
4
  export type DcgchatConfig = {
5
- enabled?: boolean;
5
+ enabled?: boolean
6
6
  /** 后端 WebSocket 地址,例如 ws://localhost:8080/openclaw/ws */
7
- wsUrl?: string;
7
+ wsUrl?: string
8
8
  /** 连接认证 token */
9
- botToken?: string;
9
+ botToken?: string
10
10
  /** 用户标识 */
11
- userId?: string;
12
- domainId?: string;
13
- appId?: string;
14
- };
11
+ userId?: string
12
+ domainId?: string
13
+ appId?: string
14
+ }
15
15
 
16
16
  export type ResolvedDcgchatAccount = {
17
- accountId: string;
18
- enabled: boolean;
19
- configured: boolean;
20
- wsUrl: string;
21
- botToken: string;
22
- userId: string;
23
- domainId?: string;
24
- appId?: string;
25
- };
17
+ accountId: string
18
+ enabled: boolean
19
+ configured: boolean
20
+ wsUrl: string
21
+ botToken: string
22
+ userId: string
23
+ domainId?: string
24
+ appId?: string
25
+ }
26
26
 
27
27
  /**
28
28
  * 下行消息:后端 → OpenClaw(用户发的消息)
@@ -33,25 +33,25 @@ export type ResolvedDcgchatAccount = {
33
33
  // text: string;
34
34
  // };
35
35
  export type InboundMessage = {
36
- messageType: string; // "openclaw_bot_chat",
37
- _userId: number;
38
- source: string; // 'server',
36
+ messageType: string // "openclaw_bot_chat",
37
+ _userId: number
38
+ source: string // 'server',
39
39
  // content: string;
40
40
  content: {
41
- bot_token: string;
42
- domain_id?: string;
43
- app_id?: string;
44
- bot_id?: string;
45
- agent_id?: string;
46
- session_id: string;
47
- message_id: string;
48
- text: string;
41
+ bot_token: string
42
+ domain_id?: string
43
+ app_id?: string
44
+ bot_id?: string
45
+ agent_id?: string
46
+ session_id: string
47
+ message_id: string
48
+ text: string
49
49
  files?: {
50
- url: string;
51
- name: string;
52
- }[];
53
- };
54
- };
50
+ url: string
51
+ name: string
52
+ }[]
53
+ }
54
+ }
55
55
 
56
56
  // {"_userId":40,"content":"{\"bot_token\":\"sk_b7f8a3e1c5d24e6f8a1b3c4d5e6f7a8b\",\"session_id\":\"1\",\"message_id\":\"1\",\"text\":\"你好\"}","messageType":"openclaw_bot_chat","msgId":398599,"source":"server","title":"OPENCLAW机器人对话"}
57
57
 
@@ -65,51 +65,62 @@ export type InboundMessage = {
65
65
  // };
66
66
 
67
67
  export type OutboundReply = {
68
- messageType: string; // "openclaw_bot_chat",
69
- _userId: number; // 100
70
- source: string; // 'client',
68
+ messageType: string // "openclaw_bot_chat",
69
+ _userId: number // 100
70
+ source: string // 'client',
71
71
  // content: string;
72
72
  content: {
73
- bot_token: string; // ""
74
- session_id: string; // ""
75
- message_id: string; // ""
76
- response: string; // ""
77
- state: string; // final, chunk
78
- domain_id?: string;
79
- app_id?: string;
80
- bot_id?: string;
81
- agent_id?: string;
82
- files?: {url: string, name: string}[];
83
- };
84
- };
73
+ bot_token: string // ""
74
+ session_id: string // ""
75
+ message_id: string // ""
76
+ response: string // ""
77
+ state: string // final, chunk
78
+ domain_id?: string
79
+ app_id?: string
80
+ bot_id?: string
81
+ agent_id?: string
82
+ files?: { url: string; name: string }[]
83
+ }
84
+ }
85
85
 
86
86
  export interface IResponse<T = unknown> {
87
87
  /** 响应状态码 */
88
- code?: number | string;
88
+ code?: number | string
89
89
  /** 响应数据 */
90
- data?: T;
90
+ data?: T
91
91
  /** 响应消息 */
92
- message?: string;
92
+ message?: string
93
93
  }
94
94
 
95
95
  export interface IStsToken {
96
- bucket: string;
97
- endPoint: string;
98
- expiration: string;
99
- ossFileKey: string;
100
- policy: string;
101
- region: string;
102
- signature: string;
103
- sourceFileName: string;
104
- stsEndPoint: string;
105
- tempAccessKeyId: string;
106
- tempAccessKeySecret: string;
107
- tempSecurityToken: string;
108
- uploadDir: string;
109
- protocol: string;
96
+ bucket: string
97
+ endPoint: string
98
+ expiration: string
99
+ ossFileKey: string
100
+ policy: string
101
+ region: string
102
+ signature: string
103
+ sourceFileName: string
104
+ stsEndPoint: string
105
+ tempAccessKeyId: string
106
+ tempAccessKeySecret: string
107
+ tempSecurityToken: string
108
+ uploadDir: string
109
+ protocol: string
110
110
  }
111
111
 
112
112
  export interface IStsTokenReq {
113
113
  sourceFileName: string
114
114
  isPrivate: number
115
115
  }
116
+
117
+ export interface IMsgParams {
118
+ userId: number
119
+ token: string
120
+ sessionId: string
121
+ messageId: string
122
+ domainId: string
123
+ appId: string
124
+ botId: string
125
+ agentId: string
126
+ }
@@ -0,0 +1,4 @@
1
+ export const ENV: 'production' | 'test' | 'develop' = 'test'
2
+
3
+
4
+ export const emptyToolText = ['/new', '/search', '/stop', '/abort', '/queue interrupt']
@@ -0,0 +1,112 @@
1
+ /** socket connection */
2
+ import type WebSocket from 'ws'
3
+
4
+ let ws: WebSocket | null = null
5
+
6
+ export function setWsConnection(next: WebSocket | null) {
7
+ ws = next
8
+ }
9
+
10
+ export function getWsConnection(): WebSocket | null {
11
+ return ws
12
+ }
13
+
14
+ // OpenClawConfig
15
+ let config: OpenClawConfig | null = null
16
+
17
+ export function setOpenClawConfig(next: OpenClawConfig | null) {
18
+ config = next
19
+ }
20
+
21
+ export function getOpenClawConfig(): OpenClawConfig | null {
22
+ return config
23
+ }
24
+
25
+ import type { ChannelLogSink, OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
26
+ import { dcgLogger } from './log.js'
27
+ import { IMsgParams } from '../types.js'
28
+
29
+ const path = require('path')
30
+ const fs = require('fs')
31
+ const os = require('os')
32
+
33
+ function getWorkspacePath() {
34
+ const workspacePath = path.join(os.homedir(), '.openclaw', 'workspace')
35
+ if (fs.existsSync(workspacePath)) {
36
+ return workspacePath
37
+ }
38
+ return null
39
+ }
40
+
41
+ let runtime: PluginRuntime | null = null
42
+ let workspaceDir: string = getWorkspacePath()
43
+
44
+ export function setWorkspaceDir(dir?: string) {
45
+ if (dir) {
46
+ workspaceDir = dir
47
+ }
48
+ }
49
+ export function getWorkspaceDir(): string {
50
+ if (!workspaceDir) {
51
+ dcgLogger?.('Workspace directory not initialized', 'error')
52
+ }
53
+ return workspaceDir
54
+ }
55
+
56
+ export function setDcgchatRuntime(next: PluginRuntime) {
57
+ runtime = next
58
+ }
59
+
60
+ export function getDcgchatRuntime(): PluginRuntime {
61
+ if (!runtime) {
62
+ dcgLogger?.('runtime not initialized', 'error')
63
+ }
64
+ return runtime as PluginRuntime
65
+ }
66
+
67
+ let msgParams = {} as IMsgParams
68
+ export function setMsgParams(params: any) {
69
+ msgParams = params
70
+ }
71
+ export function getMsgParams() {
72
+ return msgParams
73
+ }
74
+
75
+ let msgStatus: 'running' | 'finished' | '' = ''
76
+ export function setMsgStatus(status: 'running' | 'finished' | '') {
77
+ msgStatus = status
78
+ }
79
+ export function getMsgStatus() {
80
+ return msgStatus
81
+ }
82
+
83
+ const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
84
+
85
+ /** 已发送媒体去重:外层 messageId → 内层该会话下已发送的媒体 key(文件名) */
86
+ const sentMediaKeysBySession = new Map<string, Set<string>>()
87
+
88
+ function getSessionMediaSet(messageId: string): Set<string> {
89
+ let set = sentMediaKeysBySession.get(messageId)
90
+ if (!set) {
91
+ set = new Set<string>()
92
+ sentMediaKeysBySession.set(messageId, set)
93
+ }
94
+ return set
95
+ }
96
+
97
+ export function addSentMediaKey(messageId: string, url: string) {
98
+ getSessionMediaSet(messageId).add(getMediaKey(url))
99
+ }
100
+
101
+ export function hasSentMediaKey(messageId: string, url: string): boolean {
102
+ return sentMediaKeysBySession.get(messageId)?.has(getMediaKey(url)) ?? false
103
+ }
104
+
105
+ /** 不传 messageId 时清空全部会话;传入则只清空该会话 */
106
+ export function clearSentMediaKeys(messageId?: string) {
107
+ if (messageId) {
108
+ sentMediaKeysBySession.delete(messageId)
109
+ } else {
110
+ sentMediaKeysBySession.clear()
111
+ }
112
+ }
@@ -0,0 +1,15 @@
1
+ import { RuntimeEnv } from 'openclaw/plugin-sdk'
2
+
3
+ let logger: RuntimeEnv | null = null
4
+
5
+ export function setLogger(next: RuntimeEnv | null) {
6
+ logger = next
7
+ }
8
+
9
+ export function dcgLogger(message: string, type: 'log' | 'error' = 'log'): void {
10
+ if (logger) {
11
+ logger[type](`书灵墨宝🚀 ~ [${new Date().toISOString()}] ${message}`)
12
+ } else {
13
+ console[type](`书灵墨宝🚀 ~ ${new Date().toISOString()} [${"dcgchat-test"}]: ${message}`)
14
+ }
15
+ }