@wu529778790/open-im 0.1.0

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.
Files changed (58) hide show
  1. package/.env.example +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +51 -0
  4. package/dist/access/access-control.d.ts +5 -0
  5. package/dist/access/access-control.js +11 -0
  6. package/dist/adapters/claude-adapter.d.ts +7 -0
  7. package/dist/adapters/claude-adapter.js +17 -0
  8. package/dist/adapters/registry.d.ts +4 -0
  9. package/dist/adapters/registry.js +11 -0
  10. package/dist/adapters/tool-adapter.interface.d.ts +35 -0
  11. package/dist/adapters/tool-adapter.interface.js +4 -0
  12. package/dist/claude/cli-runner.d.ts +28 -0
  13. package/dist/claude/cli-runner.js +166 -0
  14. package/dist/claude/stream-parser.d.ts +17 -0
  15. package/dist/claude/stream-parser.js +50 -0
  16. package/dist/claude/types.d.ts +54 -0
  17. package/dist/claude/types.js +21 -0
  18. package/dist/cli.d.ts +2 -0
  19. package/dist/cli.js +6 -0
  20. package/dist/commands/handler.d.ts +30 -0
  21. package/dist/commands/handler.js +122 -0
  22. package/dist/config.d.ts +20 -0
  23. package/dist/config.js +88 -0
  24. package/dist/constants.d.ts +7 -0
  25. package/dist/constants.js +15 -0
  26. package/dist/hook/permission-server.d.ts +4 -0
  27. package/dist/hook/permission-server.js +12 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +76 -0
  30. package/dist/logger.d.ts +16 -0
  31. package/dist/logger.js +65 -0
  32. package/dist/queue/request-queue.d.ts +6 -0
  33. package/dist/queue/request-queue.js +43 -0
  34. package/dist/sanitize.d.ts +1 -0
  35. package/dist/sanitize.js +11 -0
  36. package/dist/session/session-manager.d.ts +26 -0
  37. package/dist/session/session-manager.js +207 -0
  38. package/dist/setup.d.ts +5 -0
  39. package/dist/setup.js +97 -0
  40. package/dist/shared/active-chats.d.ts +4 -0
  41. package/dist/shared/active-chats.js +55 -0
  42. package/dist/shared/ai-task.d.ts +42 -0
  43. package/dist/shared/ai-task.js +167 -0
  44. package/dist/shared/message-dedup.d.ts +4 -0
  45. package/dist/shared/message-dedup.js +25 -0
  46. package/dist/shared/task-cleanup.d.ts +2 -0
  47. package/dist/shared/task-cleanup.js +19 -0
  48. package/dist/shared/types.d.ts +9 -0
  49. package/dist/shared/types.js +1 -0
  50. package/dist/shared/utils.d.ts +7 -0
  51. package/dist/shared/utils.js +72 -0
  52. package/dist/telegram/client.d.ts +6 -0
  53. package/dist/telegram/client.js +27 -0
  54. package/dist/telegram/event-handler.d.ts +8 -0
  55. package/dist/telegram/event-handler.js +174 -0
  56. package/dist/telegram/message-sender.d.ts +7 -0
  57. package/dist/telegram/message-sender.js +102 -0
  58. package/package.json +52 -0
@@ -0,0 +1,122 @@
1
+ import { resolveLatestPermission, getPendingCount } from '../hook/permission-server.js';
2
+ import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
3
+ import { execFile } from 'node:child_process';
4
+ export class CommandHandler {
5
+ deps;
6
+ constructor(deps) {
7
+ this.deps = deps;
8
+ }
9
+ async dispatch(text, chatId, userId, platform, handleClaudeRequest) {
10
+ const t = text.trim();
11
+ if (platform === 'telegram' && t === '/start') {
12
+ await this.deps.sender.sendTextReply(chatId, '欢迎使用 open-im AI CLI 桥接!\n\n发送消息与 AI 交互,输入 /help 查看帮助。');
13
+ return true;
14
+ }
15
+ if (t === '/help')
16
+ return this.handleHelp(chatId, platform);
17
+ if (t === '/new')
18
+ return this.handleNew(chatId, userId);
19
+ if (t === '/pwd')
20
+ return this.handlePwd(chatId, userId);
21
+ if (t === '/status')
22
+ return this.handleStatus(chatId, userId);
23
+ if (t === '/allow' || t === '/y')
24
+ return this.handleAllow(chatId);
25
+ if (t === '/deny' || t === '/n')
26
+ return this.handleDeny(chatId);
27
+ if (t === '/cd' || t.startsWith('/cd ')) {
28
+ return this.handleCd(chatId, userId, t.slice(3).trim());
29
+ }
30
+ const cmd = t.split(/\s+/)[0];
31
+ if (TERMINAL_ONLY_COMMANDS.has(cmd)) {
32
+ await this.deps.sender.sendTextReply(chatId, `${cmd} 命令仅在终端可用。`);
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+ async handleHelp(chatId, platform) {
38
+ const help = [
39
+ '📋 可用命令:',
40
+ '',
41
+ '/help - 显示帮助',
42
+ '/new - 开始新会话',
43
+ '/status - 显示状态',
44
+ '/cd <路径> - 切换工作目录',
45
+ '/pwd - 当前工作目录',
46
+ '/allow (/y) - 允许权限请求',
47
+ '/deny (/n) - 拒绝权限请求',
48
+ ].join('\n');
49
+ await this.deps.sender.sendTextReply(chatId, help);
50
+ return true;
51
+ }
52
+ async handleNew(chatId, userId) {
53
+ const ok = this.deps.sessionManager.newSession(userId);
54
+ await this.deps.sender.sendTextReply(chatId, ok ? '✅ 已开始新会话。' : '当前没有活动会话。');
55
+ return true;
56
+ }
57
+ async handlePwd(chatId, userId) {
58
+ const workDir = this.deps.sessionManager.getWorkDir(userId);
59
+ await this.deps.sender.sendTextReply(chatId, `当前工作目录: ${workDir}`);
60
+ return true;
61
+ }
62
+ async handleStatus(chatId, userId) {
63
+ const version = await this.getClaudeVersion();
64
+ const workDir = this.deps.sessionManager.getWorkDir(userId);
65
+ const convId = this.deps.sessionManager.getConvId(userId);
66
+ const sessionId = this.deps.sessionManager.getSessionIdForConv(userId, convId);
67
+ const record = this.deps.userCosts.get(userId);
68
+ const lines = [
69
+ '📊 状态:',
70
+ '',
71
+ `AI 工具: ${this.deps.config.aiCommand}`,
72
+ `版本: ${version}`,
73
+ `工作目录: ${workDir}`,
74
+ `会话: ${sessionId ?? '无'}`,
75
+ `费用: $${record?.totalCost.toFixed(4) ?? '0.0000'}`,
76
+ ];
77
+ await this.deps.sender.sendTextReply(chatId, lines.join('\n'));
78
+ return true;
79
+ }
80
+ async handleCd(chatId, userId, dir) {
81
+ if (!dir) {
82
+ await this.deps.sender.sendTextReply(chatId, `当前目录: ${this.deps.sessionManager.getWorkDir(userId)}\n使用 /cd <路径> 切换`);
83
+ return true;
84
+ }
85
+ try {
86
+ const resolved = await this.deps.sessionManager.setWorkDir(userId, dir);
87
+ await this.deps.sender.sendTextReply(chatId, `工作目录已切换到: ${resolved}\n会话已重置。`);
88
+ }
89
+ catch (err) {
90
+ await this.deps.sender.sendTextReply(chatId, err instanceof Error ? err.message : String(err));
91
+ }
92
+ return true;
93
+ }
94
+ async handleAllow(chatId) {
95
+ const reqId = resolveLatestPermission(chatId, 'allow');
96
+ if (reqId) {
97
+ const remaining = getPendingCount(chatId);
98
+ await this.deps.sender.sendTextReply(chatId, `✅ 权限已允许${remaining > 0 ? `(还有 ${remaining} 个待确认)` : ''}`);
99
+ }
100
+ else {
101
+ await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
102
+ }
103
+ return true;
104
+ }
105
+ async handleDeny(chatId) {
106
+ const reqId = resolveLatestPermission(chatId, 'deny');
107
+ if (reqId) {
108
+ await this.deps.sender.sendTextReply(chatId, '❌ 权限已拒绝');
109
+ }
110
+ else {
111
+ await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
112
+ }
113
+ return true;
114
+ }
115
+ getClaudeVersion() {
116
+ return new Promise((resolve) => {
117
+ execFile(this.deps.config.claudeCliPath, ['--version'], { timeout: 5000 }, (err, stdout) => {
118
+ resolve(err ? '未知' : (stdout?.toString().trim() || '未知'));
119
+ });
120
+ });
121
+ }
122
+ }
@@ -0,0 +1,20 @@
1
+ import type { LogLevel } from './logger.js';
2
+ export type Platform = 'feishu' | 'telegram';
3
+ export type AiCommand = 'claude' | 'codex' | 'cursor';
4
+ export interface Config {
5
+ enabledPlatforms: Platform[];
6
+ telegramBotToken: string;
7
+ allowedUserIds: string[];
8
+ aiCommand: AiCommand;
9
+ claudeCliPath: string;
10
+ claudeWorkDir: string;
11
+ allowedBaseDirs: string[];
12
+ claudeSkipPermissions: boolean;
13
+ claudeTimeoutMs: number;
14
+ claudeModel?: string;
15
+ logDir: string;
16
+ logLevel: LogLevel;
17
+ }
18
+ /** 检测是否需要交互式配置(无 token 且无环境变量) */
19
+ export declare function needsSetup(): boolean;
20
+ export declare function loadConfig(): Config;
package/dist/config.js ADDED
@@ -0,0 +1,88 @@
1
+ try {
2
+ await import('dotenv/config');
3
+ }
4
+ catch {
5
+ /* dotenv optional */
6
+ }
7
+ import { readFileSync, accessSync, constants } from 'node:fs';
8
+ import { execFileSync } from 'node:child_process';
9
+ import { join, isAbsolute } from 'node:path';
10
+ import { APP_HOME } from './constants.js';
11
+ const CONFIG_PATH = join(APP_HOME, 'config.json');
12
+ function loadFileConfig() {
13
+ try {
14
+ return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ /** 检测是否需要交互式配置(无 token 且无环境变量) */
21
+ export function needsSetup() {
22
+ if (process.env.TELEGRAM_BOT_TOKEN)
23
+ return false;
24
+ const file = loadFileConfig();
25
+ return !file.telegramBotToken;
26
+ }
27
+ function parseCommaSeparated(value) {
28
+ return value.split(',').map((s) => s.trim()).filter(Boolean);
29
+ }
30
+ export function loadConfig() {
31
+ const file = loadFileConfig();
32
+ const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN ?? file.telegramBotToken ?? '';
33
+ const enabledPlatforms = telegramBotToken ? ['telegram'] : [];
34
+ if (enabledPlatforms.length === 0) {
35
+ throw new Error('至少需要配置 TELEGRAM_BOT_TOKEN');
36
+ }
37
+ const allowedUserIds = process.env.ALLOWED_USER_IDS !== undefined
38
+ ? parseCommaSeparated(process.env.ALLOWED_USER_IDS)
39
+ : file.allowedUserIds ?? [];
40
+ const aiCommand = (process.env.AI_COMMAND ?? file.aiCommand ?? 'claude');
41
+ const claudeCliPath = process.env.CLAUDE_CLI_PATH ?? file.claudeCliPath ?? 'claude';
42
+ const claudeWorkDir = process.env.CLAUDE_WORK_DIR ?? file.claudeWorkDir ?? process.cwd();
43
+ const allowedBaseDirs = process.env.ALLOWED_BASE_DIRS !== undefined
44
+ ? parseCommaSeparated(process.env.ALLOWED_BASE_DIRS)
45
+ : file.allowedBaseDirs ?? [];
46
+ if (allowedBaseDirs.length === 0)
47
+ allowedBaseDirs.push(claudeWorkDir);
48
+ const claudeSkipPermissions = process.env.CLAUDE_SKIP_PERMISSIONS !== undefined
49
+ ? process.env.CLAUDE_SKIP_PERMISSIONS === 'true'
50
+ : file.claudeSkipPermissions ?? true;
51
+ const claudeTimeoutMs = process.env.CLAUDE_TIMEOUT_MS !== undefined
52
+ ? parseInt(process.env.CLAUDE_TIMEOUT_MS, 10) || 600000
53
+ : file.claudeTimeoutMs ?? 600000;
54
+ if (aiCommand === 'claude') {
55
+ if (isAbsolute(claudeCliPath) || claudeCliPath.includes('/')) {
56
+ try {
57
+ accessSync(claudeCliPath, constants.F_OK | constants.X_OK);
58
+ }
59
+ catch {
60
+ throw new Error(`Claude CLI 不可执行: ${claudeCliPath}`);
61
+ }
62
+ }
63
+ else {
64
+ try {
65
+ execFileSync('which', [claudeCliPath], { stdio: 'pipe' });
66
+ }
67
+ catch {
68
+ throw new Error(`Claude CLI 在 PATH 中未找到: ${claudeCliPath}`);
69
+ }
70
+ }
71
+ }
72
+ const logDir = process.env.LOG_DIR ?? file.logDir ?? join(APP_HOME, 'logs');
73
+ const logLevel = (process.env.LOG_LEVEL?.toUpperCase() ?? file.logLevel ?? 'INFO');
74
+ return {
75
+ enabledPlatforms,
76
+ telegramBotToken,
77
+ allowedUserIds,
78
+ aiCommand,
79
+ claudeCliPath,
80
+ claudeWorkDir,
81
+ allowedBaseDirs,
82
+ claudeSkipPermissions,
83
+ claudeTimeoutMs,
84
+ claudeModel: process.env.CLAUDE_MODEL ?? file.claudeModel,
85
+ logDir,
86
+ logLevel,
87
+ };
88
+ }
@@ -0,0 +1,7 @@
1
+ export declare const APP_HOME: string;
2
+ export declare const IMAGE_DIR: string;
3
+ export declare const READ_ONLY_TOOLS: string[];
4
+ export declare const TERMINAL_ONLY_COMMANDS: Set<string>;
5
+ export declare const DEDUP_TTL_MS: number;
6
+ export declare const THROTTLE_MS = 200;
7
+ export declare const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
@@ -0,0 +1,15 @@
1
+ import { join } from 'node:path';
2
+ import { homedir, tmpdir } from 'node:os';
3
+ export const APP_HOME = join(homedir(), '.open-im');
4
+ export const IMAGE_DIR = join(tmpdir(), 'open-im-images');
5
+ export const READ_ONLY_TOOLS = [
6
+ 'Read', 'Glob', 'Grep', 'WebFetch', 'WebSearch', 'Task', 'TodoRead',
7
+ ];
8
+ export const TERMINAL_ONLY_COMMANDS = new Set([
9
+ '/context', '/rewind', '/resume', '/copy', '/export', '/config',
10
+ '/init', '/memory', '/permissions', '/theme', '/vim', '/statusline',
11
+ '/terminal-setup', '/debug', '/tasks', '/mcp', '/teleport', '/add-dir',
12
+ ]);
13
+ export const DEDUP_TTL_MS = 5 * 60 * 1000;
14
+ export const THROTTLE_MS = 200;
15
+ export const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
@@ -0,0 +1,4 @@
1
+ export declare function resolveLatestPermission(_chatId: string, _decision: 'allow' | 'deny'): string | null;
2
+ export declare function getPendingCount(_chatId: string): number;
3
+ export declare function resolvePermissionById(_requestId: string, _decision: 'allow' | 'deny'): boolean;
4
+ export declare function registerPermissionSender(_platform: string, _sender: unknown): void;
@@ -0,0 +1,12 @@
1
+ export function resolveLatestPermission(_chatId, _decision) {
2
+ return null;
3
+ }
4
+ export function getPendingCount(_chatId) {
5
+ return 0;
6
+ }
7
+ export function resolvePermissionById(_requestId, _decision) {
8
+ return false;
9
+ }
10
+ export function registerPermissionSender(_platform, _sender) {
11
+ /* stub */
12
+ }
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ import { loadConfig, needsSetup } from './config.js';
2
+ import { runInteractiveSetup } from './setup.js';
3
+ import { initTelegram, stopTelegram } from './telegram/client.js';
4
+ import { setupTelegramHandlers } from './telegram/event-handler.js';
5
+ import { sendTextReply } from './telegram/message-sender.js';
6
+ import { initAdapters } from './adapters/registry.js';
7
+ import { SessionManager } from './session/session-manager.js';
8
+ import { loadActiveChats, getActiveChatId, flushActiveChats } from './shared/active-chats.js';
9
+ import { initLogger, createLogger, closeLogger } from './logger.js';
10
+ import { createRequire } from 'node:module';
11
+ const require = createRequire(import.meta.url);
12
+ const { version: APP_VERSION } = require('../package.json');
13
+ const log = createLogger('Main');
14
+ async function sendLifecycleNotification(platform, message) {
15
+ const chatId = getActiveChatId('telegram');
16
+ if (!chatId)
17
+ return;
18
+ try {
19
+ await sendTextReply(chatId, message);
20
+ }
21
+ catch (err) {
22
+ log.debug('Failed to send lifecycle notification:', err);
23
+ }
24
+ }
25
+ export async function main() {
26
+ if (needsSetup()) {
27
+ const saved = await runInteractiveSetup();
28
+ if (!saved)
29
+ process.exit(1);
30
+ }
31
+ const config = loadConfig();
32
+ initLogger(config.logDir, config.logLevel);
33
+ loadActiveChats();
34
+ initAdapters(config);
35
+ log.info('Starting open-im bridge...');
36
+ log.info(`AI 工具: ${config.aiCommand}`);
37
+ log.info(`工作目录: ${config.claudeWorkDir}`);
38
+ const sessionManager = new SessionManager(config.claudeWorkDir, config.allowedBaseDirs);
39
+ let telegramHandle = null;
40
+ if (config.enabledPlatforms.includes('telegram')) {
41
+ await initTelegram(config, (bot) => {
42
+ telegramHandle = setupTelegramHandlers(bot, config, sessionManager);
43
+ });
44
+ }
45
+ log.info('Service is running. Press Ctrl+C to stop.');
46
+ const startupMsg = [
47
+ `🟢 open-im v${APP_VERSION} 服务已启动`,
48
+ '',
49
+ `AI 工具: ${config.aiCommand}`,
50
+ `工作目录: ${config.claudeWorkDir}`,
51
+ ].join('\n');
52
+ await sendLifecycleNotification('telegram', startupMsg).catch(() => { });
53
+ const startedAt = Date.now();
54
+ const shutdown = async () => {
55
+ log.info('Shutting down...');
56
+ const uptimeSec = Math.floor((Date.now() - startedAt) / 1000);
57
+ const m = Math.floor(uptimeSec / 60);
58
+ await sendLifecycleNotification('telegram', `🔴 open-im 服务正在关闭...\n运行时长: ${m}分钟`).catch(() => { });
59
+ telegramHandle?.stop();
60
+ stopTelegram();
61
+ sessionManager.destroy();
62
+ flushActiveChats();
63
+ closeLogger();
64
+ process.exit(0);
65
+ };
66
+ process.on('SIGINT', () => shutdown().catch(() => process.exit(1)));
67
+ process.on('SIGTERM', () => shutdown().catch(() => process.exit(1)));
68
+ }
69
+ const isEntry = process.argv[1]?.replace(/\\/g, '/').endsWith('/index.js') || process.argv[1]?.replace(/\\/g, '/').endsWith('/index.ts');
70
+ if (isEntry) {
71
+ main().catch((err) => {
72
+ console.error('Fatal error:', err);
73
+ closeLogger();
74
+ process.exit(1);
75
+ });
76
+ }
@@ -0,0 +1,16 @@
1
+ declare const LOG_LEVELS: {
2
+ readonly DEBUG: 0;
3
+ readonly INFO: 1;
4
+ readonly WARN: 2;
5
+ readonly ERROR: 3;
6
+ };
7
+ export type LogLevel = keyof typeof LOG_LEVELS;
8
+ export declare function initLogger(dir?: string, level?: LogLevel): void;
9
+ export declare function createLogger(tag: string): {
10
+ info: (msg: string, ...args: unknown[]) => void;
11
+ warn: (msg: string, ...args: unknown[]) => void;
12
+ error: (msg: string, ...args: unknown[]) => void;
13
+ debug: (msg: string, ...args: unknown[]) => void;
14
+ };
15
+ export declare function closeLogger(): void;
16
+ export {};
package/dist/logger.js ADDED
@@ -0,0 +1,65 @@
1
+ import { createWriteStream, mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { sanitize } from './sanitize.js';
4
+ import { APP_HOME } from './constants.js';
5
+ const DEFAULT_LOG_DIR = join(APP_HOME, 'logs');
6
+ const MAX_LOG_FILES = 10;
7
+ const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
8
+ let logDir = DEFAULT_LOG_DIR;
9
+ let minLevel = LOG_LEVELS.DEBUG;
10
+ let logStream;
11
+ function pad(n) {
12
+ return String(n).padStart(2, '0');
13
+ }
14
+ function getLogFileName() {
15
+ const d = new Date();
16
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}.log`;
17
+ }
18
+ function rotateOldLogs() {
19
+ try {
20
+ const files = readdirSync(logDir)
21
+ .filter((f) => f.endsWith('.log'))
22
+ .map((f) => ({ name: f, time: statSync(join(logDir, f)).mtimeMs }))
23
+ .sort((a, b) => b.time - a.time);
24
+ for (let i = MAX_LOG_FILES; i < files.length; i++) {
25
+ unlinkSync(join(logDir, files[i].name));
26
+ }
27
+ }
28
+ catch {
29
+ /* ignore */
30
+ }
31
+ }
32
+ export function initLogger(dir, level) {
33
+ if (dir)
34
+ logDir = dir;
35
+ if (level)
36
+ minLevel = LOG_LEVELS[level] ?? LOG_LEVELS.DEBUG;
37
+ if (!existsSync(logDir))
38
+ mkdirSync(logDir, { recursive: true });
39
+ rotateOldLogs();
40
+ logStream = createWriteStream(join(logDir, getLogFileName()), { flags: 'a' });
41
+ }
42
+ function write(level, tag, msg, ...args) {
43
+ if (LOG_LEVELS[level] < minLevel)
44
+ return;
45
+ const d = new Date();
46
+ const ts = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
47
+ const extra = args.length > 0 ? ' ' + args.map((a) => (a instanceof Error ? a.message : String(a))).join(' ') : '';
48
+ const line = sanitize(`${ts} [${level}] [${tag}] ${msg}${extra}\n`);
49
+ if (level === 'ERROR')
50
+ process.stderr.write(line);
51
+ else
52
+ process.stdout.write(line);
53
+ logStream?.write(line);
54
+ }
55
+ export function createLogger(tag) {
56
+ return {
57
+ info: (msg, ...args) => write('INFO', tag, msg, ...args),
58
+ warn: (msg, ...args) => write('WARN', tag, msg, ...args),
59
+ error: (msg, ...args) => write('ERROR', tag, msg, ...args),
60
+ debug: (msg, ...args) => write('DEBUG', tag, msg, ...args),
61
+ };
62
+ }
63
+ export function closeLogger() {
64
+ logStream?.end();
65
+ }
@@ -0,0 +1,6 @@
1
+ export type EnqueueResult = 'running' | 'queued' | 'rejected';
2
+ export declare class RequestQueue {
3
+ private queues;
4
+ enqueue(userId: string, convId: string, prompt: string, execute: (prompt: string) => Promise<void>): EnqueueResult;
5
+ private run;
6
+ }
@@ -0,0 +1,43 @@
1
+ import { createLogger } from '../logger.js';
2
+ const log = createLogger('Queue');
3
+ const MAX_QUEUE_SIZE = 3;
4
+ export class RequestQueue {
5
+ queues = new Map();
6
+ enqueue(userId, convId, prompt, execute) {
7
+ const key = `${userId}:${convId}`;
8
+ let q = this.queues.get(key);
9
+ if (!q) {
10
+ q = { running: false, tasks: [] };
11
+ this.queues.set(key, q);
12
+ }
13
+ if (q.running && q.tasks.length >= MAX_QUEUE_SIZE)
14
+ return 'rejected';
15
+ if (q.running) {
16
+ q.tasks.push({ prompt, execute, enqueuedAt: Date.now() });
17
+ log.info(`Queued task for ${key}`);
18
+ return 'queued';
19
+ }
20
+ q.running = true;
21
+ this.run(key, prompt, execute);
22
+ return 'running';
23
+ }
24
+ async run(key, prompt, execute) {
25
+ try {
26
+ await execute(prompt);
27
+ }
28
+ catch (err) {
29
+ log.error(`Error executing task for ${key}:`, err);
30
+ }
31
+ const q = this.queues.get(key);
32
+ if (!q)
33
+ return;
34
+ const next = q.tasks.shift();
35
+ if (next) {
36
+ setImmediate(() => this.run(key, next.prompt, next.execute));
37
+ }
38
+ else {
39
+ q.running = false;
40
+ this.queues.delete(key);
41
+ }
42
+ }
43
+ }
@@ -0,0 +1 @@
1
+ export declare function sanitize(text: string): string;
@@ -0,0 +1,11 @@
1
+ const PATTERNS = [
2
+ [/\b(sk|pk|bot)[-_][a-zA-Z0-9_-]{8,}/gi, (m) => (m.match(/^[a-zA-Z]+/)?.[0] || m.slice(0, 2)) + '_****'],
3
+ [/\b(AIza|AKIA)[a-zA-Z0-9]{12,}/g, (m) => m.slice(0, 4) + '****'],
4
+ ];
5
+ export function sanitize(text) {
6
+ let result = text;
7
+ for (const [re, replacer] of PATTERNS) {
8
+ result = result.replace(re, replacer);
9
+ }
10
+ return result;
11
+ }
@@ -0,0 +1,26 @@
1
+ export declare class SessionManager {
2
+ private sessions;
3
+ private convSessionMap;
4
+ private defaultWorkDir;
5
+ private allowedBaseDirs;
6
+ private saveTimer;
7
+ constructor(defaultWorkDir: string, allowedBaseDirs: string[]);
8
+ getSessionIdForConv(userId: string, convId: string): string | undefined;
9
+ setSessionIdForConv(userId: string, convId: string, sessionId: string): void;
10
+ getSessionIdForThread(_userId: string, _threadId: string): string | undefined;
11
+ setSessionIdForThread(userId: string, threadId: string, sessionId: string): void;
12
+ getWorkDir(userId: string): string;
13
+ getConvId(userId: string): string;
14
+ setWorkDir(userId: string, workDir: string): Promise<string>;
15
+ newSession(userId: string): boolean;
16
+ addTurns(userId: string, turns: number): number;
17
+ addTurnsForThread(userId: string, threadId: string, turns: number): number;
18
+ getModel(userId: string, threadId?: string): string | undefined;
19
+ setModel(userId: string, model: string | undefined, threadId?: string): void;
20
+ private resolveAndValidate;
21
+ private load;
22
+ private save;
23
+ private flushSync;
24
+ destroy(): void;
25
+ private doFlush;
26
+ }