@wu529778790/open-im 1.11.10 → 1.11.11-beta.1
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 +12 -10
- package/dist/cli.js +1 -32
- package/dist/config-web-auth.d.ts +1 -1
- package/dist/config-web-auth.js +24 -20
- package/dist/config-web.js +3 -1
- package/dist/index.js +2 -2
- package/dist/logger.js +54 -19
- package/dist/platform/handle-text-flow.js +10 -12
- package/dist/sanitize.js +26 -3
- package/dist/shared/keepalive.js +3 -1
- package/dist/shared/sentry.js +3 -1
- package/package.json +9 -2
- package/web/dist/assets/index-BWssx2TP.js +61 -0
- package/web/dist/assets/index-yWVkC-zc.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DKQMHruB.css +0 -1
- package/web/dist/assets/index-DmvOPUfa.js +0 -61
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ open-im start
|
|
|
74
74
|
| `/context` | 查看上下文用量 |
|
|
75
75
|
| `/plugins` | 查看已安装插件 |
|
|
76
76
|
| `/status` | 显示状态信息 |
|
|
77
|
+
| `/a` | 查看当前 AI 工具及可选列表 |
|
|
78
|
+
| `/a <工具名>` | 切换当前平台的 AI 工具(claude/codex/codebuddy/opencode) |
|
|
77
79
|
| `/cd <路径>` / `/pwd` | 切换/查看工作目录 |
|
|
78
80
|
|
|
79
81
|
### 快捷命令
|
|
@@ -90,29 +92,26 @@ open-im start
|
|
|
90
92
|
|
|
91
93
|
### 权限与确认
|
|
92
94
|
|
|
93
|
-
Claude
|
|
95
|
+
Claude(Agent SDK)、Codex、CodeBuddy、OpenCode 默认都进入**自动执行模式**:AI 工具需要权限时直接放行,不会在 IM 里卡住等待确认。
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
> ⚠️ open-im 目前没有把 Claude SDK 的权限确认弹窗转发到 IM。一旦开启原生确认流程,请求会因无人应答而卡死,所以默认全部跳过。未来接入权限 hook 后会提供细粒度控制。
|
|
98
|
+
|
|
99
|
+
如果需要恢复 Claude 的原生确认流程,可在配置文件里设置:
|
|
96
100
|
|
|
97
101
|
```json
|
|
98
102
|
{
|
|
99
103
|
"tools": {
|
|
100
104
|
"claude": {
|
|
101
|
-
"skipPermissions":
|
|
105
|
+
"skipPermissions": false
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
也可以用环境变量临时覆盖:
|
|
108
|
-
|
|
109
111
|
```bash
|
|
110
|
-
OPEN_IM_SKIP_PERMISSIONS=
|
|
111
|
-
OPEN_IM_SKIP_PERMISSIONS=false open-im start # 使用 Claude 原生确认
|
|
112
|
+
OPEN_IM_SKIP_PERMISSIONS=false open-im start # 恢复 Claude 原生确认
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
Codex、CodeBuddy、OpenCode 仍保持原来的自动执行默认行为。
|
|
115
|
-
|
|
116
115
|
## 会话接力
|
|
117
116
|
|
|
118
117
|
open-im 和 Claude Code CLI 共享 session 存储。同一目录下,手机和电脑无缝切换:
|
|
@@ -136,11 +135,14 @@ claude -c # 接上手机端的对话
|
|
|
136
135
|
|
|
137
136
|
- 配置所有平台凭证
|
|
138
137
|
- 启动/停止桥接服务
|
|
139
|
-
-
|
|
138
|
+
- 编辑配置文件(open-im / Claude / Codex / CodeBuddy / OpenCode,含 Codex 的 `auth.json` 与 `config.toml`)
|
|
139
|
+
- API 保活设置(定期发送请求延续 5 小时滚动配额,避免 token 浪费)
|
|
140
140
|
- 首次运行自动弹出设置向导
|
|
141
141
|
- 平台卡片支持展开/折叠
|
|
142
142
|
- 一键保存并启动
|
|
143
143
|
|
|
144
|
+
> 💡 通过 IM 命令 `/a <工具名>` 切换 AI 工具,或 Web 控制台修改平台配置后,**下一条消息立即生效,无需重启**桥接服务。
|
|
145
|
+
|
|
144
146
|
局域网访问:`export OPEN_IM_WEB_HOST=0.0.0.0`
|
|
145
147
|
|
|
146
148
|
## CLI 命令
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { main, needsSetup
|
|
2
|
+
import { main, needsSetup } from "./index.js";
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
import { checkAndUpdate } from "./check-update.js";
|
|
5
5
|
import { getPublicWebDashboardUrl } from "./constants.js";
|
|
@@ -20,23 +20,6 @@ function logWebDashboardAndApi() {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
async function ensureConfigured(mode) {
|
|
23
|
-
if (mode === "init") {
|
|
24
|
-
if (!process.stdin.isTTY) {
|
|
25
|
-
console.error("CLI setup requires an interactive terminal.");
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
const saved = await runInteractiveSetup();
|
|
29
|
-
if (!saved)
|
|
30
|
-
return false;
|
|
31
|
-
try {
|
|
32
|
-
loadConfig();
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
23
|
if (!needsSetup()) {
|
|
41
24
|
try {
|
|
42
25
|
loadConfig();
|
|
@@ -123,18 +106,6 @@ async function cmdRestart() {
|
|
|
123
106
|
logWebDashboardAndApi();
|
|
124
107
|
process.exit(0);
|
|
125
108
|
}
|
|
126
|
-
async function cmdInit() {
|
|
127
|
-
console.log("\nopen-im CLI setup\n");
|
|
128
|
-
const saved = await ensureConfigured("init");
|
|
129
|
-
if (!saved) {
|
|
130
|
-
console.log("\nConfiguration was not completed.");
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
console.log("\nConfiguration saved.");
|
|
134
|
-
console.log("\nYou can start the app with:");
|
|
135
|
-
console.log(" open-im start");
|
|
136
|
-
console.log(" open-im dev");
|
|
137
|
-
}
|
|
138
109
|
async function cmdDev() {
|
|
139
110
|
if (!(await ensureConfigured("dev"))) {
|
|
140
111
|
console.log("Configuration was not completed.");
|
|
@@ -165,7 +136,6 @@ Commands:
|
|
|
165
136
|
start Run the full app in the background and serve the dashboard
|
|
166
137
|
stop Stop the full app
|
|
167
138
|
restart Restart the full app in the background
|
|
168
|
-
init Run CLI setup
|
|
169
139
|
dev Run in the foreground for debugging
|
|
170
140
|
dashboard Open the web dashboard (keeps running until Ctrl+C)
|
|
171
141
|
|
|
@@ -185,7 +155,6 @@ const commands = {
|
|
|
185
155
|
start: cmdStart,
|
|
186
156
|
stop: cmdStop,
|
|
187
157
|
restart: cmdRestart,
|
|
188
|
-
init: cmdInit,
|
|
189
158
|
dev: cmdDev,
|
|
190
159
|
dashboard: cmdDashboard,
|
|
191
160
|
};
|
|
@@ -9,5 +9,5 @@ export declare function consumeLoginToken(loginToken: string): LoginTokenInfo |
|
|
|
9
9
|
export declare function createLoginToken(ttlMs: number): string;
|
|
10
10
|
export declare function createSession(request: IncomingMessage, ttlMs: number): string;
|
|
11
11
|
export declare function isSessionValid(request: IncomingMessage): boolean;
|
|
12
|
-
export declare function buildSessionCookie(sessionId: string, ttlMs: number): string;
|
|
12
|
+
export declare function buildSessionCookie(sessionId: string, ttlMs: number, isHttps?: boolean): string;
|
|
13
13
|
export declare function generateLoginUrl(host: string, port: number, loginTtlMs: number): string;
|
package/dist/config-web-auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { parse, serialize } from "cookie";
|
|
2
3
|
const pendingLogins = new Map();
|
|
3
4
|
const activeSessions = new Map();
|
|
4
5
|
export function getWebConfigHost() {
|
|
@@ -58,17 +59,16 @@ function parseCookies(request) {
|
|
|
58
59
|
const header = request.headers.cookie;
|
|
59
60
|
if (!header)
|
|
60
61
|
return {};
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
cookies[key] = decodeURIComponent(value);
|
|
62
|
+
// 使用成熟的 cookie 库,更安全可靠
|
|
63
|
+
const cookies = parse(header);
|
|
64
|
+
// 转换为 Record<string, string>,过滤掉 undefined 值
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [key, value] of Object.entries(cookies)) {
|
|
67
|
+
if (value !== undefined) {
|
|
68
|
+
result[key] = value;
|
|
69
|
+
}
|
|
70
70
|
}
|
|
71
|
-
return
|
|
71
|
+
return result;
|
|
72
72
|
}
|
|
73
73
|
function getSessionIdFromRequest(request) {
|
|
74
74
|
const cookies = parseCookies(request);
|
|
@@ -94,17 +94,21 @@ export function isSessionValid(request) {
|
|
|
94
94
|
}
|
|
95
95
|
return true;
|
|
96
96
|
}
|
|
97
|
-
export function buildSessionCookie(sessionId, ttlMs) {
|
|
97
|
+
export function buildSessionCookie(sessionId, ttlMs, isHttps = false) {
|
|
98
98
|
const maxAgeSec = Math.floor(ttlMs / 1000);
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
99
|
+
const options = {
|
|
100
|
+
path: "/",
|
|
101
|
+
httpOnly: true,
|
|
102
|
+
sameSite: "lax",
|
|
103
|
+
maxAge: maxAgeSec,
|
|
104
|
+
};
|
|
105
|
+
// 根据请求协议动态设置 Secure 标志
|
|
106
|
+
// 在生产环境(HTTPS 反代后)应该设置为 true
|
|
107
|
+
if (isHttps || process.env.NODE_ENV === "production") {
|
|
108
|
+
options.secure = true;
|
|
109
|
+
}
|
|
110
|
+
// 使用成熟的 cookie 库的 serialize 函数
|
|
111
|
+
return serialize("openim_session", sessionId, options);
|
|
108
112
|
}
|
|
109
113
|
export function generateLoginUrl(host, port, loginTtlMs) {
|
|
110
114
|
const loginToken = createLoginToken(loginTtlMs);
|
package/dist/config-web.js
CHANGED
|
@@ -68,7 +68,9 @@ export async function startWebConfigServer(options) {
|
|
|
68
68
|
// 有效的一次性登录 token:创建会话,设置 Cookie,并重定向到去掉 login_token 的 URL
|
|
69
69
|
const sessionTtlMs = 24 * 60 * 60 * 1000; // 24 小时
|
|
70
70
|
const sessionId = createSession(request, sessionTtlMs);
|
|
71
|
-
|
|
71
|
+
// 检查是否通过 HTTPS 反代访问
|
|
72
|
+
const isHttps = request.headers["x-forwarded-proto"] === "https";
|
|
73
|
+
const cookie = buildSessionCookie(sessionId, sessionTtlMs, isHttps);
|
|
72
74
|
requestUrl.searchParams.delete("login_token");
|
|
73
75
|
const redirectPath = requestUrl.pathname + (requestUrl.search ? requestUrl.search : "");
|
|
74
76
|
response.writeHead(302, mergeCors(request, {
|
package/dist/index.js
CHANGED
|
@@ -370,8 +370,8 @@ export async function main() {
|
|
|
370
370
|
if (existsSync(portFile))
|
|
371
371
|
unlinkSync(portFile);
|
|
372
372
|
}
|
|
373
|
-
catch {
|
|
374
|
-
|
|
373
|
+
catch (err) {
|
|
374
|
+
log.debug('Failed to remove port file:', err);
|
|
375
375
|
}
|
|
376
376
|
// Stop each platform: abort running tasks, then handle.stop() then module.stop()
|
|
377
377
|
for (const platform of successfulPlatforms) {
|
package/dist/logger.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { createWriteStream, mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
1
|
+
import { createWriteStream, mkdirSync, existsSync, readdirSync, statSync, unlinkSync, createReadStream } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { finished } from 'node:stream/promises';
|
|
4
|
+
import { pipeline } from 'node:stream';
|
|
5
|
+
import { createGzip } from 'node:zlib';
|
|
4
6
|
import { sanitize } from './sanitize.js';
|
|
5
7
|
import { APP_HOME } from './constants.js';
|
|
6
8
|
const DEFAULT_LOG_DIR = join(APP_HOME, 'logs');
|
|
7
9
|
const MAX_LOG_FILES = 10;
|
|
10
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
8
11
|
const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
|
9
12
|
let logDir = DEFAULT_LOG_DIR;
|
|
10
13
|
let minLevel = LOG_LEVELS.DEBUG;
|
|
@@ -17,10 +20,6 @@ function getLogFileName() {
|
|
|
17
20
|
const d = new Date();
|
|
18
21
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}.log`;
|
|
19
22
|
}
|
|
20
|
-
function getEventsFileName() {
|
|
21
|
-
const d = new Date();
|
|
22
|
-
return `events-${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}.jsonl`;
|
|
23
|
-
}
|
|
24
23
|
function rotateOldLogs() {
|
|
25
24
|
try {
|
|
26
25
|
const files = readdirSync(logDir)
|
|
@@ -31,37 +30,71 @@ function rotateOldLogs() {
|
|
|
31
30
|
unlinkSync(join(logDir, files[i].name));
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
catch {
|
|
35
|
-
|
|
33
|
+
catch (err) {
|
|
34
|
+
// 日志轮转失败不影响主流程
|
|
35
|
+
console.error('Failed to rotate log files:', err);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 压缩日志文件
|
|
40
|
+
*/
|
|
41
|
+
function compressFile(filePath) {
|
|
39
42
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const gzip = createGzip();
|
|
44
|
+
const source = createReadStream(filePath);
|
|
45
|
+
const destination = createWriteStream(filePath + '.gz');
|
|
46
|
+
pipeline(source, gzip, destination, (err) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
console.error('Failed to compress log file:', err);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// 压缩成功,删除原文件
|
|
52
|
+
try {
|
|
53
|
+
unlinkSync(filePath);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
console.error('Failed to delete original log file:', e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error('Failed to compress log file:', err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 检查日志文件大小,必要时轮转
|
|
67
|
+
*/
|
|
68
|
+
function checkLogSize() {
|
|
69
|
+
if (!logStream)
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
const currentLogPath = join(logDir, getLogFileName());
|
|
73
|
+
const stats = statSync(currentLogPath);
|
|
74
|
+
if (stats.size > MAX_LOG_SIZE) {
|
|
75
|
+
// 关闭当前日志流
|
|
76
|
+
logStream.end();
|
|
77
|
+
logStream = undefined;
|
|
78
|
+
// 压缩当前日志文件
|
|
79
|
+
compressFile(currentLogPath);
|
|
80
|
+
// 创建新的日志文件
|
|
81
|
+
logStream = createWriteStream(join(logDir, getLogFileName()), { flags: 'a' });
|
|
46
82
|
}
|
|
47
83
|
}
|
|
48
|
-
catch {
|
|
49
|
-
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('Failed to check log size:', err);
|
|
50
86
|
}
|
|
51
87
|
}
|
|
52
88
|
export function initLogger(dirOrOpts, level, telemetry) {
|
|
53
89
|
let dir;
|
|
54
90
|
let lev;
|
|
55
|
-
let tel;
|
|
56
91
|
if (dirOrOpts && typeof dirOrOpts === 'object' && !Array.isArray(dirOrOpts)) {
|
|
57
92
|
dir = dirOrOpts.logDir;
|
|
58
93
|
lev = dirOrOpts.logLevel;
|
|
59
|
-
tel = dirOrOpts.telemetry;
|
|
60
94
|
}
|
|
61
95
|
else {
|
|
62
96
|
dir = dirOrOpts;
|
|
63
97
|
lev = level;
|
|
64
|
-
tel = telemetry;
|
|
65
98
|
}
|
|
66
99
|
if (dir)
|
|
67
100
|
logDir = dir;
|
|
@@ -90,6 +123,8 @@ function write(level, tag, msg, ...args) {
|
|
|
90
123
|
else
|
|
91
124
|
process.stdout.write(line);
|
|
92
125
|
logStream?.write(line);
|
|
126
|
+
// 检查日志文件大小
|
|
127
|
+
checkLogSize();
|
|
93
128
|
}
|
|
94
129
|
export function createLogger(tag) {
|
|
95
130
|
return {
|
|
@@ -20,24 +20,22 @@ import { setChatUser } from '../shared/chat-user-map.js';
|
|
|
20
20
|
import { createLogger, auditLog } from '../logger.js';
|
|
21
21
|
import { handleEnqueueResult, DEFAULT_QUEUE_FULL_MESSAGE, DEFAULT_QUEUED_MESSAGE } from '../shared/utils.js';
|
|
22
22
|
import { walWrite, walCommit } from '../shared/message-wal.js';
|
|
23
|
+
import { TTLCache } from '@isaacs/ttlcache';
|
|
23
24
|
/* ── 幂等性:消息去重 ── */
|
|
24
25
|
const DEDUP_TTL_MS = 60_000; // 1 分钟内的相同 msgId 视为重复
|
|
25
|
-
const
|
|
26
|
+
const DEDUP_MAX_SIZE = 1000; // 最大缓存条目数
|
|
27
|
+
// 使用 TTLCache 自动管理过期条目,避免内存泄漏
|
|
28
|
+
const dedupCache = new TTLCache({
|
|
29
|
+
max: DEDUP_MAX_SIZE,
|
|
30
|
+
ttl: DEDUP_TTL_MS,
|
|
31
|
+
updateAgeOnGet: false, // 访问时不更新过期时间
|
|
32
|
+
});
|
|
26
33
|
function isDuplicate(msgId) {
|
|
27
34
|
if (!msgId)
|
|
28
35
|
return false;
|
|
29
|
-
|
|
30
|
-
const prev = dedupCache.get(msgId);
|
|
31
|
-
if (prev && now - prev < DEDUP_TTL_MS)
|
|
36
|
+
if (dedupCache.has(msgId))
|
|
32
37
|
return true;
|
|
33
|
-
dedupCache.set(msgId,
|
|
34
|
-
// 清理过期条目
|
|
35
|
-
if (dedupCache.size > 1000) {
|
|
36
|
-
for (const [k, v] of dedupCache) {
|
|
37
|
-
if (now - v > DEDUP_TTL_MS)
|
|
38
|
-
dedupCache.delete(k);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
38
|
+
dedupCache.set(msgId, true);
|
|
41
39
|
return false;
|
|
42
40
|
}
|
|
43
41
|
const log = createLogger('TextFlow');
|
package/dist/sanitize.js
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
const PATTERNS = [
|
|
2
|
-
|
|
3
|
-
[/\
|
|
4
|
-
|
|
2
|
+
// OpenAI / Anthropic / Claude API keys
|
|
3
|
+
[/\bsk-[a-zA-Z0-9]{32,}\b/g, (m) => 'sk-****' + m.slice(-4)],
|
|
4
|
+
// AWS Access Key
|
|
5
|
+
[/\b(AKIA|ASIA)[A-Z0-9]{16}\b/g, (m) => m.slice(0, 4) + '****'],
|
|
6
|
+
// GitHub Personal Access Token
|
|
7
|
+
[/\b(ghp|github_pat)_[a-zA-Z0-9_]{36,}\b/g, (m) => m.slice(0, 4) + '_****'],
|
|
8
|
+
// GitLab Personal Access Token
|
|
9
|
+
[/\bglpat-[a-zA-Z0-9_-]{20,}\b/g, (m) => 'glpat-****'],
|
|
10
|
+
// Telegram Bot Token
|
|
11
|
+
[/\b[0-9]{8,}:[a-zA-Z0-9_-]{35,}\b/g, (m) => m.split(':')[0] + ':****'],
|
|
12
|
+
// Anthropic API Key
|
|
13
|
+
[/\bant-[a-zA-Z0-9]{32,}\b/g, (m) => 'ant-****' + m.slice(-4)],
|
|
14
|
+
// OpenRouter API Key
|
|
15
|
+
[/\bsk-or-[a-zA-Z0-9]{32,}\b/g, (m) => 'sk-or-****' + m.slice(-4)],
|
|
16
|
+
// Google API Key
|
|
17
|
+
[/\bAIza[a-zA-Z0-9_-]{35}\b/g, (m) => 'AIza****'],
|
|
18
|
+
// Azure API Key
|
|
19
|
+
[/\b[a-f0-9]{32}:[a-zA-Z0-9]{44}\b/g, (m) => m.split(':')[0] + ':****'],
|
|
20
|
+
// 飞书/钉钉等 Bot Token
|
|
21
|
+
[/\b(bot)[-_][a-zA-Z0-9_-]{20,}\b/gi, (m) => 'bot_****'],
|
|
22
|
+
// 通用 API Key (更严格的匹配,避免误报)
|
|
23
|
+
[/\b(api_key|apikey|api-key|API_KEY|APIKEY)[\s]*[:=][\s]*[a-zA-Z0-9_-]{16,}\b/gi,
|
|
24
|
+
(m) => m.split(/[:=]/)[0] + '=****'],
|
|
25
|
+
// 密码模式
|
|
26
|
+
[/\b(password|passwd|pwd)[\s]*[:=][\s]*[^\s]{8,}\b/gi,
|
|
27
|
+
(m) => m.split(/[:=]/)[0] + '=****'],
|
|
5
28
|
];
|
|
6
29
|
export function sanitize(text) {
|
|
7
30
|
let result = text;
|
package/dist/shared/keepalive.js
CHANGED
package/dist/shared/sentry.js
CHANGED
|
@@ -9,7 +9,9 @@ import * as Sentry from '@sentry/node';
|
|
|
9
9
|
import { createLogger } from '../logger.js';
|
|
10
10
|
const log = createLogger('Sentry');
|
|
11
11
|
// 开发者的 Sentry DSN(所有 open-im 实例共享)
|
|
12
|
-
|
|
12
|
+
// 注意:此 DSN 仅用于 open-im 自身的错误追踪
|
|
13
|
+
// 用户可在环境变量中配置自己的 DSN
|
|
14
|
+
const DEFAULT_DSN = process.env.OPEN_IM_SENTRY_DSN ?? 'https://cc5ad094c1229b2a2ff23ab54b0fd807@o4508612762861568.ingest.us.sentry.io/4511583989727232';
|
|
13
15
|
let initialized = false;
|
|
14
16
|
/**
|
|
15
17
|
* 清理 PII(用户数据)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.11-beta.1",
|
|
4
4
|
"description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,11 +54,13 @@
|
|
|
54
54
|
"@anthropic-ai/claude-agent-sdk": "^0.3.179",
|
|
55
55
|
"@anthropic-ai/sdk": "^0.104.2",
|
|
56
56
|
"@aws-sdk/client-s3": "^3.1035.0",
|
|
57
|
-
"@
|
|
57
|
+
"@isaacs/ttlcache": "^2.1.5",
|
|
58
|
+
"@larksuiteoapi/node-sdk": "^1.67.0",
|
|
58
59
|
"@opencode-ai/sdk": "^1.17.9",
|
|
59
60
|
"@sentry/node": "^10.58.0",
|
|
60
61
|
"@types/qrcode": "^1.5.6",
|
|
61
62
|
"centrifuge": "^5.5.3",
|
|
63
|
+
"cookie": "^1.1.1",
|
|
62
64
|
"dingtalk-stream": "^2.1.4",
|
|
63
65
|
"edge-tts": "^1.0.1",
|
|
64
66
|
"https-proxy-agent": "^9.1.0",
|
|
@@ -74,15 +76,20 @@
|
|
|
74
76
|
"@types/node": "^20.0.0",
|
|
75
77
|
"@types/prompts": "^2.4.9",
|
|
76
78
|
"@types/ws": "^8.5.13",
|
|
79
|
+
"axios": "^1.18.1",
|
|
77
80
|
"dotenv": "^17.3.1",
|
|
78
81
|
"eslint": "^9.15.0",
|
|
79
82
|
"globals": "^15.12.0",
|
|
80
83
|
"tsx": "^4.0.0",
|
|
81
84
|
"typescript": "^5.0.0",
|
|
82
85
|
"typescript-eslint": "^8.58.0",
|
|
86
|
+
"vite": "^8.1.0",
|
|
83
87
|
"vitest": "^4.1.2"
|
|
84
88
|
},
|
|
85
89
|
"engines": {
|
|
86
90
|
"node": ">=20"
|
|
91
|
+
},
|
|
92
|
+
"overrides": {
|
|
93
|
+
"axios": "^1.18.1"
|
|
87
94
|
}
|
|
88
95
|
}
|