@qihoo/tuitui-openclaw-channel 1.0.32 → 1.0.34

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/README.md CHANGED
@@ -10,20 +10,15 @@
10
10
  <a href="https://github.com/openclaw/openclaw"><img alt="OpenClaw" src="https://img.shields.io/badge/OpenClaw-%3E%3D2026.3.13-0A7CFF"></a>
11
11
  </p>
12
12
 
13
- 针对 OpenClaw 的推推机器人 Channel 渠道插件,基于 WebSocket 订阅模式接收消息、基于 HTTP API 发送消息,无需公网 IP。
13
+ OpenClaw 的推推机器人 Channel 渠道插件
14
14
 
15
15
  ## 功能特性
16
16
 
17
- - WebSocket 订阅模式,无需 Webhook 和公网环境依赖
18
17
  - 支持私聊、群聊和 @机器人、团队帖子等
19
- - 支持文本、图片、语音、视频、文件、帖子
20
- - 支持多 Agent、多机器人绑定、状态检测同步
21
-
22
- ## 文档入口
23
-
24
- - 推推的公开网站:<https://tuitui.cn>
25
- - 了解推推机器人:<https://easydoc.soft.360.cn/doc?project=38ed795130e25371ef319aeb60d5b4fa&doc=d1f11ba4cac8886301ede97079df824e&config=menu_toc>
26
-
18
+ - 支持文本、图片、语音、视频、文件、频道帖子
19
+ - 支持多 Agent + 多机器人绑定
20
+ - 支持安全控制(私聊模式与白名单、群聊模式与白名单)
21
+ - WebSocket 订阅模式,个人电脑可用,无需 Webhook 和公网ip依赖
27
22
 
28
23
  ## 安装与配置指南
29
24
 
@@ -32,30 +27,33 @@
32
27
  龙虾+推推插件配置指南
33
28
 
34
29
  ### 什么是龙虾推推channel插件
35
- 在openclaw上安装推推插件后,可以通过推推与你的openclaw聊天。无需公网ip个人电脑也可以用。
30
+ 在openclaw上安装推推插件后,可以通过推推与你的openclaw聊天。
36
31
 
37
32
  ### 第一步、创建你的专属推推机器人
38
33
  推推搜索 推推机器人助手,和它私聊。使用 /创建 可自助创建一个机器人,得到appid和secret后用。
39
34
  另外你也可以对它使用 /改头像 指令。
40
35
 
41
36
  ### 第二步、安装openclaw 推推插件
37
+ 在终端命令行运行命令
38
+
42
39
  `openclaw plugins install @qihoo/tuitui-openclaw-channel`
43
- (如果以前你装过推推插件zip包内测版,需要先把原来的目录删了,不然装不上)
44
40
 
45
- 有可能会遇到 clawHub 429 报错、npm install failed Error: package.json missing openclaw.hooks 等。
46
- 均为下载环节不稳定导致,可以多试几次或参考下文的“本地源码链接安装”。
41
+ 如果遇到 clawHub 429 报错、npm install failed Error: package.json missing openclaw.hooks 等。
42
+ 均为下载连接npm仓库不稳定导致,可以多试几次。
47
43
 
48
44
  ### 第三步、配置插件。
49
- 打开龙虾dashboard页面,频道->Tuitui,在appid和secret填入第一步获取的,并找到 Enabled 开启,下方点保存。
45
+ 打开龙虾dashboard页面,频道->Tuitui,在appid和secret填入第一步获取的密钥,下方点保存。
50
46
 
51
47
  ### 第四步、与你的机器人聊天
52
48
 
53
49
  私聊你的龙虾机器人,会提示配对。复制提示消息末尾的那行指令,龙虾主人在终端执行你复制的那行命令,然后就能聊了。
54
50
 
55
- 群聊的话,你把机器人拉到群能力。需要@机器人触发。第一次会提示主人配置群白名单,主人复制群id在dashboard上配置白名单以后就能聊了。
51
+ 如果不想配对,可以在dashboard中把Dm Policy切换为allowlist,然后从Allow From为你的账号添加白名单。
52
+
53
+ 群聊的话,你把机器人拉到群里。需要@机器人触发。主人可以直接使用。其他人@机器人会提示让主人配置白名单,主人复制群id在dashboard上配置白名单以后就能聊了。
56
54
 
57
55
  ## 插件升级
58
- 后续如果更新,可以用下述命令升级
56
+ 后续如果更新,可以用下述命令升级插件
59
57
  `openclaw plugins update tuitui-openclaw-channel`
60
58
 
61
59
  ## 安全备忘
@@ -87,3 +85,8 @@ openclaw plugins install -l .
87
85
  }
88
86
  ```
89
87
 
88
+
89
+ ## 参考文档
90
+
91
+ - 推推的公开网站:<https://tuitui.cn>
92
+ - 了解推推机器人:<https://easydoc.soft.360.cn/doc?project=38ed795130e25371ef319aeb60d5b4fa&doc=d1f11ba4cac8886301ede97079df824e&config=menu_toc>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qihoo/tuitui-openclaw-channel",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "maintainers": [
5
5
  {
6
6
  "name": "huzunjie",
package/src/chat_base.ts CHANGED
@@ -9,6 +9,8 @@ export type ChatType = typeof CHAT_TYPE_DIRECT | typeof CHAT_TYPE_GROUP | typeof
9
9
 
10
10
  export function guessChatType(chatId: string): ChatType {
11
11
  if (chatId.startsWith("teams_")) return CHAT_TYPE_CHANNEL;
12
+ const isMobileNumber = /^1[3-9]\d{9}$/.test(chatId);
13
+ if (isMobileNumber) return CHAT_TYPE_DIRECT; // fix: 部分租户号为手机号被误猜测为群id的问题。大陆手机号格式
12
14
  if (/^\d+$/.test(chatId)) return CHAT_TYPE_GROUP;
13
15
  return CHAT_TYPE_DIRECT;
14
16
  }
package/src/filespace.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CHANNEL_ID } from "./const";
2
- import { tuituiRobotApi, downloadUrl, tuituiRobotUpload} from "./robot_api"
2
+ import { tuituiRobotApi, tuituiRobotUpload} from "./robot_api"
3
3
  import {CHAT_TYPE_CHANNEL, parseSessionKey} from "./chat_base"
4
4
  import {getChannelInfoByChannelId} from "./robot_helper"
5
5
 
@@ -120,7 +120,7 @@ async function ensureFolderPath(
120
120
 
121
121
  // 构建父目录ID映射,便于查找
122
122
  const parentIdToNodes = new Map<string, any[]>();
123
- allNodes.forEach(node => {
123
+ allNodes.forEach((node: any) => {
124
124
  const parentId = node.parent_id || "";
125
125
  if (!parentIdToNodes.has(parentId)) {
126
126
  parentIdToNodes.set(parentId, []);
package/src/inbound.ts CHANGED
@@ -13,14 +13,8 @@
13
13
  import type { TuiTuiInboundMessage, TuiTuiOutboundDeliverOptions } from './types';
14
14
  import { CHANNEL_ID } from './const';
15
15
  import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,ChatType,teamsParseChatId,teamsBuildChatId} from "./chat_base"
16
- import {
17
- tuituiEmojiReaction,
18
- sendTextMsg,
19
- sendPageMsg,
20
- sendMediaMsg,
21
- get_announcement,
22
- get_team_members,
23
- } from "./outbound";
16
+ import {tuituiEmojiReaction, sendTextMsg, sendPageMsg, sendMediaMsg } from "./outbound";
17
+ import { teams_get_members, teams_get_members_text, teams_get_announcement } from "./teams_api"
24
18
  import { parseChatMessageBody } from './inbound_body_parse';
25
19
  import { parseAllowFroms } from './utils';
26
20
  import { addUnmentionedHistory, popUnmentionedHistories } from "./histories";
@@ -139,7 +133,7 @@ async function dispatchReply(ctx: any, cfg: any, account: InboundAccount, payloa
139
133
  },
140
134
 
141
135
  onReplyStart: () => {
142
- log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, Agent reply started for ${payload.tuituiAccount ?? payload.tuituiUid}`);
136
+ log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, Agent is typing to ${payload.tuituiAccount}`);
143
137
  },
144
138
  },
145
139
  });
@@ -207,7 +201,7 @@ async function isChannelCoordAuthorized(tuituiAccount: string, apiRuntime: any,
207
201
 
208
202
  // 当前 thread 中有白名单用户发帖,则协调员继承该权限
209
203
  const [members, posts] = await Promise.all([
210
- get_team_members(account, team_id),
204
+ teams_get_members(account, team_id),
211
205
  getPostChainByChatId(account, chat_id),
212
206
  ]);
213
207
  if (!members?.length || !posts?.length) return false;
@@ -387,20 +381,7 @@ async function parse_teams_post(payload: ChatPayload, msgData: any, account: Inb
387
381
  payload.text += `[文件] ${file?.name} : ${file?.url} \n`;
388
382
  }
389
383
  }
390
-
391
- if(_session_ctx_injected.checkAndRecord(accountId + "_" + payload.chatId)) {
392
- // 注入上下文
393
- // 解决机器人不知道自己是谁的问题
394
- if(payload.botName) {
395
- payload.text += `\n[备忘]\n你在当前session中的名字叫: ${payload.botName}\n 如果有人@这个名字,就是在命令你`;
396
- }
397
- // 公告
398
- const annnouncement = await get_announcement(account, channel_id, false);
399
- if(annnouncement) {
400
- payload.text += `\n[当前公告内容如下--有需要时可参考]\n${annnouncement}`;
401
- }
402
- }
403
- }
384
+ }
404
385
 
405
386
  if (!msgData.at_me && account.requireMention) {
406
387
  log?.info?.(`[${CHANNEL_ID}] AccountId: ${accountId}, ignore teams post (not mentioned), add to history ${chatId} -> ${payload.text}`);
@@ -408,6 +389,26 @@ async function parse_teams_post(payload: ChatPayload, msgData: any, account: Inb
408
389
  return false;
409
390
  }
410
391
 
392
+ if(_session_ctx_injected.checkAndRecord(accountId + "_" + payload.chatId)) {
393
+ // 注入上下文
394
+ // 解决机器人不知道自己是谁的问题
395
+ if(payload.botName) {
396
+ payload.text += `\n\n*** 参考信息: 你在当前session中的名字叫: ${payload.botName} 如果有人@这个名字,就是在命令你 ***\n`;
397
+ }
398
+ // 公告
399
+ const annnouncement = await teams_get_announcement(account, channel_id, false);
400
+ if(annnouncement) {
401
+ payload.text += "\n\n*** 参考信息: 当前公告内容如下 ***\n";
402
+ payload.text += "```\n" + annnouncement + "\n```";
403
+ }
404
+
405
+ // 成员
406
+ const members = await teams_get_members_text(account, team_id);
407
+ if(members) {
408
+ payload.text += "\n\n*** 参考信息: 当前团队成员清单如下,每行一个成员名。如果需要询问成员、或者发送给成员时,需要用 @名字 的语法;并且@时必须从如下清单中匹配到最接近的,禁止@不存在的成员 ***\n";
409
+ payload.text += "```\n" + members + "\n```";
410
+ }
411
+ }
411
412
 
412
413
  // 私聊白名单对团队仍然生效,限制特定人@时,可以不配置groupAllowFrom,而是配置 allowFrom
413
414
  if(await isAllowAccount(tuituiAccount, apiRuntime, account)){
package/src/monitor.ts CHANGED
@@ -15,10 +15,12 @@ import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
15
15
  import { CHANNEL_ID } from './const';
16
16
  import { tuituiRobotApi } from './robot_api';
17
17
  import {parseChannelIdBySessionKey} from "./chat_base"
18
+ import { monitorEnabledDefault } from './accounts';
18
19
 
19
20
  // 批量上报参数
20
21
  const BATCH_MAX_SIZE = 100; // 达到此条数立即 flush
21
22
  const BATCH_INTERVAL_MS = 5_000; // 定时 flush 间隔(ms)
23
+ const PAYLOAD_SIZE_LIMIT = 100 * 1024; // 单条 payload 兜底截断:100 KB
22
24
 
23
25
  // ─────────────────────────────────────────────────────────────
24
26
  // 上报载荷结构
@@ -109,6 +111,11 @@ function enqueue(target: MonitorTarget, payload: MonitorPayload): void {
109
111
  batchQueues.set(target.accountId, queue);
110
112
  }
111
113
 
114
+ // 兜底:data 字段超过 100 KB 时暴力截断
115
+ if (payload.data.length > PAYLOAD_SIZE_LIMIT) {
116
+ payload.data = payload.data.slice(0, PAYLOAD_SIZE_LIMIT) + '...[truncated]';
117
+ }
118
+
112
119
  queue.items.push(payload);
113
120
 
114
121
  // 达到上限,立即 flush
@@ -149,23 +156,30 @@ function resolveMonitorTargets(cfg: any): MonitorTarget[] {
149
156
  accountAgentId.get(accountId) ?? 'main';
150
157
 
151
158
  // 默认账户
152
- if (channelCfg.monitorEnabled === true) {
153
- targets.push({
154
- accountId: DEFAULT_ACCOUNT_ID,
155
- appId: channelCfg.appId || '',
156
- appSecret: channelCfg.appSecret || '',
157
- agentId: resolveAgentId(DEFAULT_ACCOUNT_ID),
158
- });
159
+ if ((channelCfg.monitorEnabled ?? monitorEnabledDefault) === true) {
160
+ const appId = channelCfg.appId || '';
161
+ const appSecret = channelCfg.appSecret || '';
162
+ if (appId && appSecret) {
163
+ targets.push({
164
+ accountId: DEFAULT_ACCOUNT_ID,
165
+ appId,
166
+ appSecret,
167
+ agentId: resolveAgentId(DEFAULT_ACCOUNT_ID),
168
+ });
169
+ }
159
170
  }
160
171
 
161
172
  // 子账户
162
173
  if (channelCfg.accounts) {
163
174
  for (const [accountId, acct] of Object.entries(channelCfg.accounts) as [string, any][]) {
164
- if (acct?.monitorEnabled !== true) continue;
175
+ if ((acct?.monitorEnabled ?? monitorEnabledDefault) !== true) continue;
176
+ const appId = acct?.appId || channelCfg.appId || '';
177
+ const appSecret = acct?.appSecret || channelCfg.appSecret || '';
178
+ if (!appId || !appSecret) continue;
165
179
  targets.push({
166
180
  accountId,
167
- appId: acct?.appId || channelCfg.appId || '',
168
- appSecret: acct?.appSecret || channelCfg.appSecret || '',
181
+ appId,
182
+ appSecret,
169
183
  agentId: resolveAgentId(accountId),
170
184
  });
171
185
  }
@@ -227,6 +241,11 @@ function trimData(eventType: MonitorEventType, data: unknown): unknown {
227
241
  const d = data as any;
228
242
  switch (eventType) {
229
243
  case 'agent_end':
244
+ if (Array.isArray(d?.messages)) {
245
+ return { ...d, messages: d.messages.slice(-10).map(truncateContent) };
246
+ }
247
+ return d;
248
+
230
249
  // case 'before_prompt_build':
231
250
  // case 'before_agent_start':
232
251
  case 'before_compaction':
package/src/outbound.ts CHANGED
@@ -216,30 +216,3 @@ export async function sendMediaMsg(
216
216
  await postTuituiMsg(account, msg, auditCtx);
217
217
  }
218
218
  }
219
-
220
-
221
- // TODO: 支持群公告
222
- export async function get_announcement(account: any, id: any, id_is_session: boolean = true): Promise<any> {
223
- let channel_id = id;
224
- if(id_is_session) {
225
- channel_id = parseChannelIdBySessionKey(id);
226
- if(!channel_id) {
227
- return "";
228
- }
229
- }
230
-
231
- const payload = {channel_id: channel_id};
232
- const body = await tuituiRobotApi(account, '/teams/channel/info', payload);
233
- //console.log("info", data);
234
- const announcement = body?.datas?.info?.announcement;
235
- return announcement;
236
- }
237
-
238
-
239
- export async function get_team_members(account: any, team_id: string): Promise<any> {
240
- const payload = {team_id: team_id};
241
- const body = await tuituiRobotApi(account, '/teams/member/list', payload);
242
- const members = body?.datas?.members;
243
- //console.log("info", members);
244
- return members;
245
- }
package/src/robot_api.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { readFileSync, existsSync, statSync } from 'node:fs';
2
2
  import { basename } from 'node:path';
3
- import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk/tlon';
4
3
  import { CHANNEL_ID } from "./const";
5
4
  import { TUITUI_SSRF_POLICY, getTuituiApiHost } from "./env"
6
5
 
@@ -34,8 +33,8 @@ export async function tuituiRobotApi(account: any, api: string, payload: any, lo
34
33
  fetch_fun = _fetchForm;
35
34
  }
36
35
 
37
- const { response, release } = await fetch_fun(url, payload);
38
36
  try {
37
+ const response = await fetch_fun(url, payload);
39
38
  const bodyText = await response.text();
40
39
 
41
40
  if (!response.ok) {
@@ -52,41 +51,26 @@ export async function tuituiRobotApi(account: any, api: string, payload: any, lo
52
51
  console.error(`[${CHANNEL_ID}] ${api} error:`, err);
53
52
  throw err;
54
53
  } finally {
55
- await release();
56
54
  if (log) {
57
- const endTime = Date.now(); // 记录结束时间
58
- const duration = endTime - startTime; // 计算耗时
55
+ const endTime = Date.now();
56
+ const duration = endTime - startTime;
59
57
  console.log(`[${CHANNEL_ID}] ${api} response took ${duration}ms`);
60
58
  }
61
59
  }
62
60
  }
63
61
 
64
- function _fetch(opts: any): Promise<any> {
65
- return fetchWithSsrFGuard({
66
- policy: TUITUI_SSRF_POLICY,
67
- ...opts,
68
- })
69
- }
70
- function _fetchJson(url: string, json: any, auditCtx: string = "tuitui.api.call"): Promise<any> {
71
- return _fetch({
72
- url,
73
- init: {
74
- method: 'POST',
75
- headers: { 'Content-Type': 'application/json' },
76
- body: JSON.stringify(json),
77
- },
78
- auditCtx
62
+ function _fetchJson(url: string, json: any): Promise<Response> {
63
+ return fetch(url, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify(json),
79
67
  });
80
68
  }
81
69
 
82
- function _fetchForm(url: string, form: any, auditCtx: string = "tuitui.api.call"): Promise<any> {
83
- return _fetch({
84
- url,
85
- init: {
86
- method: 'POST',
87
- body: form,
88
- },
89
- auditCtx
70
+ function _fetchForm(url: string, form: any): Promise<Response> {
71
+ return fetch(url, {
72
+ method: 'POST',
73
+ body: form,
90
74
  });
91
75
  }
92
76
 
@@ -96,10 +80,29 @@ function _fetchForm(url: string, form: any, auditCtx: string = "tuitui.api.call"
96
80
  * @returns Object with buffer, filename and content type
97
81
  */
98
82
  export async function downloadUrl(url: string): Promise<{ buffer: ArrayBuffer; filename: string; contentType: string }> {
99
- const { response, release } = await _fetch({
83
+ console.log(`[${CHANNEL_ID}] downloadUrl prepair ssrf`);
84
+
85
+ let fetchWithSsrFGuard: any;
86
+ try {
87
+ const ssrfRuntimeImport: any = await import('openclaw/plugin-sdk/ssrf-runtime');
88
+ fetchWithSsrFGuard = (ssrfRuntimeImport.default ?? ssrfRuntimeImport).fetchWithSsrFGuard;
89
+ } catch {
90
+ try {
91
+ fetchWithSsrFGuard = (await import('openclaw/plugin-sdk/tlon')).fetchWithSsrFGuard;
92
+ } catch {}
93
+ }
94
+
95
+ if (!fetchWithSsrFGuard) {
96
+ throw new Error(`[${CHANNEL_ID}] fetchWithSsrFGuard() API import failed`);
97
+ }
98
+
99
+ console.log(`[${CHANNEL_ID}] downloadUrl start ${url}`);
100
+
101
+ const { response, release } = await fetchWithSsrFGuard({
102
+ policy: TUITUI_SSRF_POLICY,
100
103
  url,
101
104
  init: { method: 'GET' },
102
- auditCtx: "tuitui.download"
105
+ auditContext: "tuitui.download",
103
106
  });
104
107
 
105
108
  try {
@@ -127,6 +130,8 @@ export async function downloadUrl(url: string): Promise<{ buffer: ArrayBuffer; f
127
130
  if (match) filename = decodeURIComponent(match[1]);
128
131
  }
129
132
 
133
+ console.log(`[${CHANNEL_ID}] downloadUrl ok`);
134
+
130
135
  return { buffer, filename, contentType };
131
136
  } finally {
132
137
  await release();
@@ -0,0 +1,38 @@
1
+
2
+ import { CHANNEL_ID } from "./const";
3
+ import { tuituiRobotApi, tuituiRobotUpload } from "./robot_api"
4
+ import { CHAT_TYPE_GROUP, CHAT_TYPE_DIRECT, CHAT_TYPE_CHANNEL, ChatType, teamsParseChatId, parseChannelIdBySessionKey } from "./chat_base";
5
+
6
+ // TODO: 支持群公告
7
+ export async function teams_get_announcement(account: any, id: any, id_is_session: boolean = true): Promise<any> {
8
+ let channel_id = id;
9
+ if(id_is_session) {
10
+ channel_id = parseChannelIdBySessionKey(id);
11
+ if(!channel_id) {
12
+ return "";
13
+ }
14
+ }
15
+
16
+ const payload = {channel_id: channel_id};
17
+ const body = await tuituiRobotApi(account, '/teams/channel/info', payload);
18
+ //console.log("info", data);
19
+ const announcement = body?.datas?.info?.announcement;
20
+ return announcement;
21
+ }
22
+
23
+
24
+ export async function teams_get_members(account: any, team_id: string): Promise<any> {
25
+ const payload = {team_id: team_id};
26
+ const body = await tuituiRobotApi(account, '/teams/member/list', payload);
27
+ const members = body?.datas?.members;
28
+ //console.log("info", members);
29
+ return members;
30
+ }
31
+
32
+ export async function teams_get_members_text(account: any, team_id: string): Promise<string> {
33
+ const members = await teams_get_members(account, team_id);
34
+ if (!members?.length) return "";
35
+
36
+ return members.map((m: any) => String(m?.name ?? "")).join("\n");
37
+ }
38
+
package/src/tools.ts CHANGED
@@ -3,7 +3,8 @@ import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
3
3
  import { resolveAccount } from "./accounts"
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { CHANNEL_ID } from './const';
6
- import {sendTextMsg, get_announcement} from "./outbound"
6
+ import {sendTextMsg} from "./outbound"
7
+ import { teams_get_announcement } from "./teams_api"
7
8
  import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,guessChatType, teamsBuildChatId} from "./chat_base"
8
9
  import {getChatRecord, getChannelInfoById} from "./chat_record"
9
10
  import {file_space_list, file_space_add} from "./filespace"
@@ -108,7 +109,7 @@ const tuitui_get_announcement_factory = (ctx: OpenClawPluginToolContext) => {
108
109
  if(!account || !account.enabled || !account.appId || !account.appSecret) {
109
110
  return tool_errmsg(`invalid tuitui account ${ctx.agentAccountId}`);
110
111
  }
111
- return await get_announcement(account, ctx.sessionKey);
112
+ return await teams_get_announcement(account, ctx.sessionKey);
112
113
  },
113
114
  };
114
115
  };
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "outDir": "./dist",
14
+ "rootDir": "./",
15
+ "removeComments": false,
16
+ "sourceMap": true,
17
+ "inlineSources": true,
18
+ "importHelpers": true,
19
+ "resolveJsonModule": true,
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "openclaw/*": ["node_modules/openclaw/dist/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "index.ts",
27
+ "src/**/*",
28
+ "openclaw.plugin.json"
29
+ ],
30
+ "exclude": [
31
+ "node_modules",
32
+ "dist"
33
+ ]
34
+ }