@oyasmi/pipiclaw 0.1.1 → 0.2.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.
- package/CHANGELOG.md +2 -1
- package/README.md +73 -11
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +47 -12
- package/dist/agent.js.map +1 -1
- package/dist/commands.d.ts +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +22 -0
- package/dist/commands.js.map +1 -1
- package/dist/context.d.ts +2 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +4 -4
- package/dist/context.js.map +1 -1
- package/dist/dingtalk.d.ts +2 -0
- package/dist/dingtalk.d.ts.map +1 -1
- package/dist/dingtalk.js +24 -9
- package/dist/dingtalk.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +37 -3
- package/dist/main.js.map +1 -1
- package/dist/store.d.ts +2 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context management for pipiclaw.\n *\n * Uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - PipiclawSettingsManager: Simple settings for pipiclaw (compaction, retry, model preferences)\n */\n\nimport type { UserMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionManager, SessionMessageEntry } from \"@mariozechner/pi-coding-agent\";\nimport { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\ninterface LogMessage {\n\tdate?: string;\n\tts?: string;\n\tuser?: string;\n\tuserName?: string;\n\ttext?: string;\n\tisBot?: boolean;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * Uses byte-offset tracking for incremental reads (only processes new content)\n * and timestamp-based dedup for crash safety.\n *\n * This ensures that messages logged while mom wasn't running (channel chatter,\n * messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param channelDir - Path to channel directory containing log.jsonl\n * @param excludeTs - Timestamp of current message (will be added via prompt(), not sync)\n * @returns Number of messages synced\n */\nexport function syncLogToSessionManager(\n\tsessionManager: SessionManager,\n\tchannelDir: string,\n\texcludeTs?: string,\n): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst syncOffsetFile = join(channelDir, \".sync-offset\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read sync offset (byte position of last processed content)\n\tlet offset = 0;\n\tif (existsSync(syncOffsetFile)) {\n\t\ttry {\n\t\t\toffset = Number(readFileSync(syncOffsetFile, \"utf-8\").trim()) || 0;\n\t\t} catch {\n\t\t\toffset = 0;\n\t\t}\n\t}\n\n\t// Check file size\n\tconst fileStats = statSync(logFile);\n\tif (fileStats.size < offset) {\n\t\t// File was truncated or rotated — reset to beginning\n\t\toffset = 0;\n\t}\n\tif (fileStats.size === offset) {\n\t\treturn 0; // No new content\n\t}\n\n\t// Read only new bytes from log.jsonl\n\tconst bytesToRead = fileStats.size - offset;\n\tconst fd = openSync(logFile, \"r\");\n\tlet newContent: string;\n\ttry {\n\t\tconst buffer = Buffer.alloc(bytesToRead);\n\t\treadSync(fd, buffer, 0, bytesToRead, offset);\n\t\tnewContent = buffer.toString(\"utf-8\");\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n\n\tconst newLines = newContent.trim().split(\"\\n\").filter(Boolean);\n\tif (newLines.length === 0) {\n\t\twriteFileSync(syncOffsetFile, String(fileStats.size));\n\t\treturn 0;\n\t}\n\n\t// Build set of existing user message timestamps for crash-safe dedup.\n\t// This prevents duplicates if the process crashed after appendMessage()\n\t// but before writing the sync offset.\n\tconst existingTimestamps = new Set<number>();\n\tfor (const entry of sessionManager.getEntries()) {\n\t\tif (entry.type === \"message\") {\n\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\tconst msg = msgEntry.message as { role?: string; timestamp?: number };\n\t\t\tif (msg.role === \"user\" && typeof msg.timestamp === \"number\") {\n\t\t\t\texistingTimestamps.add(msg.timestamp);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Process new log lines\n\tconst newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n\tfor (const line of newLines) {\n\t\ttry {\n\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\tconst ts = logMsg.ts;\n\t\t\tconst date = logMsg.date;\n\t\t\tif (!ts || !date) continue;\n\n\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\tif (excludeTs && ts === excludeTs) continue;\n\n\t\t\t// Skip bot messages — they're managed by SessionManager\n\t\t\tif (logMsg.isBot) continue;\n\n\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\n\t\t\t// Skip if already in context (crash recovery dedup)\n\t\t\tif (existingTimestamps.has(msgTime)) continue;\n\t\t\texistingTimestamps.add(msgTime); // Track within this batch\n\n\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\t\t\tnewMessages.push({\n\t\t\t\ttimestamp: msgTime,\n\t\t\t\tmessage: {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: messageText }],\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\tif (newMessages.length > 0) {\n\t\tnewMessages.sort((a, b) => a.timestamp - b.timestamp);\n\t\tfor (const { message } of newMessages) {\n\t\t\tsessionManager.appendMessage(message);\n\t\t}\n\t}\n\n\t// Update sync offset to current file end\n\twriteFileSync(syncOffsetFile, String(fileStats.size));\n\n\treturn newMessages.length;\n}\n\n// ============================================================================\n// PipiclawSettingsManager - Simple settings for pipiclaw\n// ============================================================================\n\nexport interface PipiclawCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface PipiclawRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface PipiclawSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<PipiclawCompactionSettings>;\n\tretry?: Partial<PipiclawRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: PipiclawCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: PipiclawRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for pipiclaw.\n * Stores global settings in the pipiclaw root directory.\n */\nexport class PipiclawSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: PipiclawSettings;\n\n\tconstructor(baseDir: string) {\n\t\tthis.settingsPath = join(baseDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): PipiclawSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): PipiclawCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): PipiclawRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as PipiclawSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\";\n\t}\n\n\tsetSteeringMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op\n\t}\n\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\";\n\t}\n\n\tsetFollowUpMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n\n\t// Image settings\n\tgetImageAutoResize(): boolean {\n\t\treturn false;\n\t}\n\n\tsetImageAutoResize(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetBlockImages(): boolean {\n\t\treturn false;\n\t}\n\n\tsetBlockImages(_blocked: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetShowImages(): boolean {\n\t\treturn false;\n\t}\n\n\tsetShowImages(_show: boolean): void {\n\t\t// No-op\n\t}\n\n\t// Compaction details\n\tgetCompactionReserveTokens(): number {\n\t\treturn DEFAULT_COMPACTION.reserveTokens;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn DEFAULT_COMPACTION.keepRecentTokens;\n\t}\n\n\tgetBranchSummarySettings(): { reserveTokens: number } {\n\t\treturn { reserveTokens: 16384 };\n\t}\n\n\t// Thinking\n\tgetHideThinkingBlock(): boolean {\n\t\treturn false;\n\t}\n\n\tsetHideThinkingBlock(_hide: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetThinkingBudgets(): undefined {\n\t\treturn undefined;\n\t}\n\n\t// Shell\n\tgetShellPath(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetShellPath(_path: string | undefined): void {\n\t\t// No-op\n\t}\n\n\tgetShellCommandPrefix(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetShellCommandPrefix(_prefix: string | undefined): void {\n\t\t// No-op\n\t}\n\n\t// Misc settings stubs\n\tgetQuietStartup(): boolean {\n\t\treturn true;\n\t}\n\n\tsetQuietStartup(_quiet: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn true;\n\t}\n\n\tsetCollapseChangelog(_collapse: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetTransport(): string {\n\t\treturn \"stdio\";\n\t}\n\n\tsetTransport(_transport: string): void {\n\t\t// No-op\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetTheme(_theme: string): void {\n\t\t// No-op\n\t}\n\n\tgetPackages(): unknown[] {\n\t\treturn [];\n\t}\n\n\tsetPackages(_packages: unknown[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectPackages(_packages: unknown[]): void {\n\t\t// No-op\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetExtensionPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectExtensionPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetSkillPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetSkillPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectSkillPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetPromptTemplatePaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetPromptTemplatePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectPromptTemplatePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetThemePaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetThemePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectThemePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetEnableSkillCommands(): boolean {\n\t\treturn false;\n\t}\n\n\tsetEnableSkillCommands(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetEnabledModels(): string[] | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetEnabledModels(_patterns: string[] | undefined): void {\n\t\t// No-op\n\t}\n\n\tgetDoubleEscapeAction(): \"none\" {\n\t\treturn \"none\";\n\t}\n\n\tsetDoubleEscapeAction(_action: string): void {\n\t\t// No-op\n\t}\n\n\tgetShowHardwareCursor(): boolean {\n\t\treturn false;\n\t}\n\n\tsetShowHardwareCursor(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetClearOnShrink(): boolean {\n\t\treturn false;\n\t}\n\n\tsetClearOnShrink(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetEditorPaddingX(): number {\n\t\treturn 0;\n\t}\n\n\tsetEditorPaddingX(_padding: number): void {\n\t\t// No-op\n\t}\n\n\tgetAutocompleteMaxVisible(): number {\n\t\treturn 10;\n\t}\n\n\tsetAutocompleteMaxVisible(_maxVisible: number): void {\n\t\t// No-op\n\t}\n\n\tgetCodeBlockIndent(): string {\n\t\treturn \" \";\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetLastChangelogVersion(_version: string): void {\n\t\t// No-op\n\t}\n\n\tsetDefaultProvider(_provider: string): void {\n\t\t// No-op\n\t}\n\n\tsetDefaultModel(_modelId: string): void {\n\t\t// No-op\n\t}\n\n\tgetGlobalSettings(): object {\n\t\treturn {};\n\t}\n\n\tgetProjectSettings(): object {\n\t\treturn {};\n\t}\n\n\treload(): void {\n\t\tthis.settings = this.load();\n\t}\n\n\tapplyOverrides(_overrides: Partial<PipiclawSettings>): void {\n\t\t// No-op\n\t}\n\n\tflush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tdrainErrors(): unknown[] {\n\t\treturn [];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context management for pipiclaw.\n *\n * Uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - PipiclawSettingsManager: Simple settings for pipiclaw (compaction, retry, model preferences)\n */\n\nimport type { UserMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionManager, SessionMessageEntry } from \"@mariozechner/pi-coding-agent\";\nimport { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\ninterface LogMessage {\n\tdate?: string;\n\tts?: string;\n\tuser?: string;\n\tuserName?: string;\n\ttext?: string;\n\tisBot?: boolean;\n\tskipContextSync?: boolean;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * Uses byte-offset tracking for incremental reads (only processes new content)\n * and timestamp-based dedup for crash safety.\n *\n * This ensures that messages logged while pipiclaw wasn't running (channel chatter,\n * externally appended user messages) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param channelDir - Path to channel directory containing log.jsonl\n * @param excludeTs - Timestamp of current message (will be added via prompt(), not sync)\n * @returns Number of messages synced\n */\nexport function syncLogToSessionManager(\n\tsessionManager: SessionManager,\n\tchannelDir: string,\n\texcludeTs?: string,\n): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst syncOffsetFile = join(channelDir, \".sync-offset\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read sync offset (byte position of last processed content)\n\tlet offset = 0;\n\tif (existsSync(syncOffsetFile)) {\n\t\ttry {\n\t\t\toffset = Number(readFileSync(syncOffsetFile, \"utf-8\").trim()) || 0;\n\t\t} catch {\n\t\t\toffset = 0;\n\t\t}\n\t}\n\n\t// Check file size\n\tconst fileStats = statSync(logFile);\n\tif (fileStats.size < offset) {\n\t\t// File was truncated or rotated — reset to beginning\n\t\toffset = 0;\n\t}\n\tif (fileStats.size === offset) {\n\t\treturn 0; // No new content\n\t}\n\n\t// Read only new bytes from log.jsonl\n\tconst bytesToRead = fileStats.size - offset;\n\tconst fd = openSync(logFile, \"r\");\n\tlet newContent: string;\n\ttry {\n\t\tconst buffer = Buffer.alloc(bytesToRead);\n\t\treadSync(fd, buffer, 0, bytesToRead, offset);\n\t\tnewContent = buffer.toString(\"utf-8\");\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n\n\tconst newLines = newContent.trim().split(\"\\n\").filter(Boolean);\n\tif (newLines.length === 0) {\n\t\twriteFileSync(syncOffsetFile, String(fileStats.size));\n\t\treturn 0;\n\t}\n\n\t// Build set of existing user message timestamps for crash-safe dedup.\n\t// This prevents duplicates if the process crashed after appendMessage()\n\t// but before writing the sync offset.\n\tconst existingTimestamps = new Set<number>();\n\tfor (const entry of sessionManager.getEntries()) {\n\t\tif (entry.type === \"message\") {\n\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\tconst msg = msgEntry.message as { role?: string; timestamp?: number };\n\t\t\tif (msg.role === \"user\" && typeof msg.timestamp === \"number\") {\n\t\t\t\texistingTimestamps.add(msg.timestamp);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Process new log lines\n\tconst newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n\tfor (const line of newLines) {\n\t\ttry {\n\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\tconst ts = logMsg.ts;\n\t\t\tconst date = logMsg.date;\n\t\t\tif (!ts || !date) continue;\n\n\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\tif (excludeTs && ts === excludeTs) continue;\n\n\t\t\t// Skip bot messages and user inputs already delivered directly to AgentSession.\n\t\t\tif (logMsg.isBot || logMsg.skipContextSync) continue;\n\n\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\n\t\t\t// Skip if already in context (crash recovery dedup)\n\t\t\tif (existingTimestamps.has(msgTime)) continue;\n\t\t\texistingTimestamps.add(msgTime); // Track within this batch\n\n\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\t\t\tnewMessages.push({\n\t\t\t\ttimestamp: msgTime,\n\t\t\t\tmessage: {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: messageText }],\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t},\n\t\t\t});\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\tif (newMessages.length > 0) {\n\t\tnewMessages.sort((a, b) => a.timestamp - b.timestamp);\n\t\tfor (const { message } of newMessages) {\n\t\t\tsessionManager.appendMessage(message);\n\t\t}\n\t}\n\n\t// Update sync offset to current file end\n\twriteFileSync(syncOffsetFile, String(fileStats.size));\n\n\treturn newMessages.length;\n}\n\n// ============================================================================\n// PipiclawSettingsManager - Simple settings for pipiclaw\n// ============================================================================\n\nexport interface PipiclawCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface PipiclawRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface PipiclawSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<PipiclawCompactionSettings>;\n\tretry?: Partial<PipiclawRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: PipiclawCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: PipiclawRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for pipiclaw.\n * Stores global settings in the pipiclaw root directory.\n */\nexport class PipiclawSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: PipiclawSettings;\n\n\tconstructor(baseDir: string) {\n\t\tthis.settingsPath = join(baseDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): PipiclawSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): PipiclawCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): PipiclawRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as PipiclawSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\";\n\t}\n\n\tsetSteeringMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op\n\t}\n\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\";\n\t}\n\n\tsetFollowUpMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n\n\t// Image settings\n\tgetImageAutoResize(): boolean {\n\t\treturn false;\n\t}\n\n\tsetImageAutoResize(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetBlockImages(): boolean {\n\t\treturn false;\n\t}\n\n\tsetBlockImages(_blocked: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetShowImages(): boolean {\n\t\treturn false;\n\t}\n\n\tsetShowImages(_show: boolean): void {\n\t\t// No-op\n\t}\n\n\t// Compaction details\n\tgetCompactionReserveTokens(): number {\n\t\treturn DEFAULT_COMPACTION.reserveTokens;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn DEFAULT_COMPACTION.keepRecentTokens;\n\t}\n\n\tgetBranchSummarySettings(): { reserveTokens: number } {\n\t\treturn { reserveTokens: 16384 };\n\t}\n\n\t// Thinking\n\tgetHideThinkingBlock(): boolean {\n\t\treturn false;\n\t}\n\n\tsetHideThinkingBlock(_hide: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetThinkingBudgets(): undefined {\n\t\treturn undefined;\n\t}\n\n\t// Shell\n\tgetShellPath(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetShellPath(_path: string | undefined): void {\n\t\t// No-op\n\t}\n\n\tgetShellCommandPrefix(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetShellCommandPrefix(_prefix: string | undefined): void {\n\t\t// No-op\n\t}\n\n\t// Misc settings stubs\n\tgetQuietStartup(): boolean {\n\t\treturn true;\n\t}\n\n\tsetQuietStartup(_quiet: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn true;\n\t}\n\n\tsetCollapseChangelog(_collapse: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetTransport(): string {\n\t\treturn \"stdio\";\n\t}\n\n\tsetTransport(_transport: string): void {\n\t\t// No-op\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetTheme(_theme: string): void {\n\t\t// No-op\n\t}\n\n\tgetPackages(): unknown[] {\n\t\treturn [];\n\t}\n\n\tsetPackages(_packages: unknown[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectPackages(_packages: unknown[]): void {\n\t\t// No-op\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetExtensionPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectExtensionPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetSkillPaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetSkillPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectSkillPaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetPromptTemplatePaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetPromptTemplatePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectPromptTemplatePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetThemePaths(): string[] {\n\t\treturn [];\n\t}\n\n\tsetThemePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tsetProjectThemePaths(_paths: string[]): void {\n\t\t// No-op\n\t}\n\n\tgetEnableSkillCommands(): boolean {\n\t\treturn false;\n\t}\n\n\tsetEnableSkillCommands(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetEnabledModels(): string[] | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetEnabledModels(_patterns: string[] | undefined): void {\n\t\t// No-op\n\t}\n\n\tgetDoubleEscapeAction(): \"none\" {\n\t\treturn \"none\";\n\t}\n\n\tsetDoubleEscapeAction(_action: string): void {\n\t\t// No-op\n\t}\n\n\tgetShowHardwareCursor(): boolean {\n\t\treturn false;\n\t}\n\n\tsetShowHardwareCursor(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetClearOnShrink(): boolean {\n\t\treturn false;\n\t}\n\n\tsetClearOnShrink(_enabled: boolean): void {\n\t\t// No-op\n\t}\n\n\tgetEditorPaddingX(): number {\n\t\treturn 0;\n\t}\n\n\tsetEditorPaddingX(_padding: number): void {\n\t\t// No-op\n\t}\n\n\tgetAutocompleteMaxVisible(): number {\n\t\treturn 10;\n\t}\n\n\tsetAutocompleteMaxVisible(_maxVisible: number): void {\n\t\t// No-op\n\t}\n\n\tgetCodeBlockIndent(): string {\n\t\treturn \" \";\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetLastChangelogVersion(_version: string): void {\n\t\t// No-op\n\t}\n\n\tsetDefaultProvider(_provider: string): void {\n\t\t// No-op\n\t}\n\n\tsetDefaultModel(_modelId: string): void {\n\t\t// No-op\n\t}\n\n\tgetGlobalSettings(): object {\n\t\treturn {};\n\t}\n\n\tgetProjectSettings(): object {\n\t\treturn {};\n\t}\n\n\treload(): void {\n\t\tthis.settings = this.load();\n\t}\n\n\tapplyOverrides(_overrides: Partial<PipiclawSettings>): void {\n\t\t// No-op\n\t}\n\n\tflush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tdrainErrors(): unknown[] {\n\t\treturn [];\n\t}\n}\n"]}
|
package/dist/dingtalk.d.ts
CHANGED
|
@@ -37,10 +37,12 @@ export interface DingTalkContext {
|
|
|
37
37
|
flush: () => Promise<void>;
|
|
38
38
|
close: () => Promise<void>;
|
|
39
39
|
}
|
|
40
|
+
export type BusyMessageMode = "steer" | "followUp";
|
|
40
41
|
export interface DingTalkHandler {
|
|
41
42
|
isRunning(channelId: string): boolean;
|
|
42
43
|
handleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;
|
|
43
44
|
handleStop(channelId: string, bot: DingTalkBot): Promise<void>;
|
|
45
|
+
handleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<void>;
|
|
44
46
|
}
|
|
45
47
|
export declare class DingTalkBot {
|
|
46
48
|
private handler;
|
package/dist/dingtalk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dingtalk.d.ts","sourceRoot":"","sources":["../src/dingtalk.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;KACX,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC/B,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAmED,qBAAa,WAAW;IACvB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAAiB;IAG/B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,WAAW,CAAK;IAGxB,OAAO,CAAC,WAAW,CAA6B;IAGhD,OAAO,CAAC,QAAQ,CAAuC;IAGvD,OAAO,CAAC,MAAM,CAAmC;IAGjD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,uBAAuB,CAAc;IAC7C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAG9B,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,iBAAiB,CAAgB;IAEzC,YAAY,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAG3D;IAED;;;OAGG;IACH,OAAO,CAAC,aAAa;IAcf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA6B3B;IAED,OAAO,CAAC,gBAAgB;YAgCV,WAAW;IAuFzB,IAAI,SAQH;IAED;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAiB1C;IAMD;;OAEG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKjD;IAED;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBlG;IAED;;;OAGG;IACG,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa/E;IAED;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMvE;IAED,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEnC;IAED;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDjE;YAMa,UAAU;YAoEV,UAAU;YAyDV,cAAc;IAsC5B,OAAO,CAAC,cAAc;YAoBR,eAAe;IAuE7B,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,mBAAmB;IA+B3B,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,uBAAuB;CAI/B","sourcesContent":["/**\n * DingTalk communication layer using dingtalk-stream SDK with AI Card streaming.\n *\n * Handles:\n * - Receiving messages via DingTalk Stream Mode (DWClient)\n * - Responding via AI Card (streaming) or plain markdown (fallback)\n * - Access token management\n * - Per-channel message queuing\n */\nimport axios from \"axios\";\nimport { DWClient, type DWClientDownStream, type RobotMessage, TOPIC_ROBOT } from \"dingtalk-stream\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DingTalkConfig {\n\tclientId: string;\n\tclientSecret: string;\n\trobotCode?: string;\n\tcardTemplateId?: string;\n\tcardTemplateKey?: string;\n\tallowFrom?: string[];\n\tstateDir?: string;\n}\n\nexport interface DingTalkEvent {\n\ttype: \"dm\" | \"group\";\n\tchannelId: string; // dm_{staffId} or group_{conversationId}\n\tts: string;\n\tuser: string; // sender staff id\n\tuserName: string; // sender nickname\n\ttext: string;\n\tconversationId: string;\n\tconversationType: string; // \"1\" = DM, \"2\" = group\n}\n\nexport interface DingTalkContext {\n\tmessage: {\n\t\ttext: string;\n\t\trawText: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\tchannel: string;\n\t\tts: string;\n\t};\n\tchannelName?: string;\n\trespond: (text: string, shouldLog?: boolean) => Promise<void>;\n\trespondPlain: (text: string, shouldLog?: boolean) => Promise<boolean>;\n\treplaceMessage: (text: string) => Promise<void>;\n\trespondInThread: (text: string) => Promise<void>;\n\tsetTyping: (isTyping: boolean) => Promise<void>;\n\tsetWorking: (working: boolean) => Promise<void>;\n\tdeleteMessage: () => Promise<void>;\n\tflush: () => Promise<void>;\n\tclose: () => Promise<void>;\n}\n\nexport interface DingTalkHandler {\n\tisRunning(channelId: string): boolean;\n\thandleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;\n\thandleStop(channelId: string, bot: DingTalkBot): Promise<void>;\n}\n\n// ============================================================================\n// AI Card State\n// ============================================================================\n\ninterface AICard {\n\tinstanceId: string;\n\tconversationId: string;\n\taccessToken: string;\n\ttemplateKey: string;\n\tcreatedAt: number;\n\tlastUpdated: number;\n\tcontent: string;\n\tfinished: boolean;\n}\n\ninterface ConversationMeta {\n\tconversationId: string;\n\tconversationType: string;\n\tsenderId: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n\tprivate queue: QueuedWork[] = [];\n\tprivate processing = false;\n\n\tenqueue(work: QueuedWork): void {\n\t\tthis.queue.push(work);\n\t\tthis.processNext();\n\t}\n\n\tsize(): number {\n\t\treturn this.queue.length;\n\t}\n\n\tprivate async processNext(): Promise<void> {\n\t\tif (this.processing || this.queue.length === 0) return;\n\t\tthis.processing = true;\n\t\tconst work = this.queue.shift()!;\n\t\ttry {\n\t\t\tawait work();\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Queue error\", err instanceof Error ? err.message : String(err));\n\t\t}\n\t\tthis.processing = false;\n\t\tthis.processNext();\n\t}\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DINGTALK_API = \"https://api.dingtalk.com\";\nconst TOKEN_REFRESH_SECS = 90 * 60; // 1.5 hours (tokens expire after 2 hours)\n\n// ============================================================================\n// DingTalkBot\n// ============================================================================\n\nexport class DingTalkBot {\n\tprivate handler: DingTalkHandler;\n\tprivate config: DingTalkConfig;\n\n\t// Access token cache\n\tprivate accessToken: string | null = null;\n\tprivate tokenExpiry = 0;\n\n\t// Active AI cards: channelId → AICard\n\tprivate activeCards = new Map<string, AICard>();\n\n\t// Conversation metadata cache: channelId → metadata\n\tprivate convMeta = new Map<string, ConversationMeta>();\n\n\t// Per-channel queues\n\tprivate queues = new Map<string, ChannelQueue>();\n\n\t// Connection stability\n\tprivate client: DWClient | null = null;\n\tprivate lastSocketAvailableTime = Date.now();\n\tprivate activeMessageProcessing = false;\n\tprivate keepAliveTimer: NodeJS.Timeout | null = null;\n\tprivate isReconnecting = false;\n\tprivate isStopped = false;\n\tprivate reconnectAttempts = 0;\n\n\t// Deduplication cache (Set for O(1) lookup, order array for FIFO eviction)\n\tprivate processedIds = new Set<string>();\n\tprivate processedIdsOrder: string[] = [];\n\n\tconstructor(handler: DingTalkHandler, config: DingTalkConfig) {\n\t\tthis.handler = handler;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Mark an ID as processed. Returns true if this is a new ID, false if already seen.\n\t * Maintains a FIFO buffer of at most 200 entries.\n\t */\n\tprivate markProcessed(id: string): boolean {\n\t\tif (this.processedIds.has(id)) return false;\n\t\tthis.processedIds.add(id);\n\t\tthis.processedIdsOrder.push(id);\n\t\twhile (this.processedIdsOrder.length > 200) {\n\t\t\tthis.processedIds.delete(this.processedIdsOrder.shift()!);\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// Public API\n\t// ==========================================================================\n\n\tasync start(): Promise<void> {\n\t\tif (!this.config.clientId || !this.config.clientSecret) {\n\t\t\tlog.logWarning(\"DingTalk: clientId / clientSecret not configured\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.cardTemplateId) {\n\t\t\tlog.logWarning(\"DingTalk: cardTemplateId not configured — AI Card streaming will not work\");\n\t\t}\n\n\t\tlog.logInfo(`DingTalk: initializing stream (clientId=${this.config.clientId.substring(0, 8)}…)`);\n\n\t\tif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\t\t\taxios.defaults.proxy = false;\n\t\t}\n\n\t\tthis.client = new DWClient({\n\t\t\tclientId: this.config.clientId,\n\t\t\tclientSecret: this.config.clientSecret,\n\t\t\tautoReconnect: false,\n\t\t\tkeepAlive: false,\n\t\t} as any);\n\n\t\tthis.client.registerCallbackListener(TOPIC_ROBOT, (msg: DWClientDownStream) => {\n\t\t\treturn this.handleRawMessage(msg);\n\t\t});\n\n\t\tlog.logConnected();\n\t\tawait this.doReconnect(true); // Initial connection\n\t}\n\n\tprivate handleRawMessage(msg: DWClientDownStream): { status: \"SUCCESS\"; message: string } {\n\t\t// 1. Immediate ACK\n\t\tif (msg.headers?.messageId && this.client) {\n\t\t\tthis.client.socketCallBackResponse(msg.headers.messageId, { status: \"SUCCESS\", message: \"OK\" });\n\t\t}\n\n\t\t// 2. Protocol deduplication\n\t\tconst messageId = msg.headers?.messageId;\n\t\tif (messageId && !this.markProcessed(messageId)) {\n\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t}\n\n\t\ttry {\n\t\t\tconst data: RobotMessage = typeof msg.data === \"string\" ? JSON.parse(msg.data) : msg.data;\n\n\t\t\t// 3. Business logic deduplication\n\t\t\tconst msgId = (data as any).msgId;\n\t\t\tif (msgId && !this.markProcessed(msgId)) {\n\t\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t\t}\n\n\t\t\t// Fire-and-forget processing\n\t\t\tthis.onStreamMessage(data).catch((err: unknown) => {\n\t\t\t\tlog.logWarning(\"DingTalk handler error\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"DingTalk: failed to parse message\", err instanceof Error ? err.message : String(err));\n\t\t}\n\n\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t}\n\n\tprivate async doReconnect(immediate = false) {\n\t\tif (this.isReconnecting || this.isStopped || !this.client) return;\n\t\tthis.isReconnecting = true;\n\t\tlet connectionFailed = false;\n\n\t\tif (!immediate && this.reconnectAttempts > 0) {\n\t\t\tconst delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);\n\t\t\tlog.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t}\n\n\t\ttry {\n\t\t\tconst socket = (this.client as any).socket;\n\t\t\tif (socket?.readyState === 1 || socket?.readyState === 3) {\n\t\t\t\tawait (this.client as any).disconnect();\n\t\t\t}\n\n\t\t\tawait this.client.connect();\n\n\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\tthis.reconnectAttempts = 0; // Success, reset backoff\n\t\t\tlog.logInfo(\"DingTalk: connected to stream.\");\n\n\t\t\t// Setup keep alive\n\t\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\t\tthis.keepAliveTimer = setInterval(() => {\n\t\t\t\tif (this.isStopped) return;\n\n\t\t\t\tconst elapsed = Date.now() - this.lastSocketAvailableTime;\n\t\t\t\tif (elapsed > 90 * 1000 && !this.activeMessageProcessing) {\n\t\t\t\t\tlog.logWarning(\"DingTalk: connection timeout detected (>90s). Keeping active where possible...\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst s = (this.client as any)?.socket;\n\t\t\t\t\tif (s?.readyState === 1) {\n\t\t\t\t\t\ts.ping();\n\t\t\t\t\t}\n\t\t\t\t} catch (_err) {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}, 30 * 1000);\n\n\t\t\t// Setup native socket events\n\t\t\tconst s = (this.client as any).socket;\n\n\t\t\ts?.on(\"pong\", () => {\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t});\n\n\t\t\ts?.on(\"close\", (code: number, reason: string) => {\n\t\t\t\tlog.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);\n\t\t\t\tif (this.isStopped) return;\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.doReconnect(true).catch((err) => {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: reconnect failed\", err instanceof Error ? err.message : String(err));\n\t\t\t\t\t});\n\t\t\t\t}, 1000);\n\t\t\t});\n\n\t\t\ts?.on(\"message\", (raw: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(raw);\n\t\t\t\t\tif (msg.type === \"SYSTEM\" && msg.headers?.topic === \"disconnect\") {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: disconnect event received from server.\");\n\t\t\t\t\t\tif (!this.isStopped) {\n\t\t\t\t\t\t\tthis.doReconnect(true).catch(() => {});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\t// skip\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthis.reconnectAttempts++;\n\t\t\tconnectionFailed = true;\n\t\t\tlog.logWarning(\"DingTalk: connection failed\", err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tthis.isReconnecting = false;\n\t\t}\n\n\t\t// Auto-retry on failure with exponential backoff\n\t\tif (connectionFailed && !this.isStopped) {\n\t\t\tthis.doReconnect().catch(() => {});\n\t\t}\n\t}\n\n\tstop() {\n\t\tthis.isStopped = true;\n\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\t(this.client as any).disconnect();\n\t\t\t} catch (_e) {}\n\t\t}\n\t}\n\n\t/**\n\t * Enqueue an event for processing.\n\t * Returns true if enqueued, false if queue is full (max 5).\n\t */\n\tenqueueEvent(event: DingTalkEvent): boolean {\n\t\tconst queue = this.getQueue(event.channelId);\n\t\tif (queue.size() >= 5) {\n\t\t\tlog.logWarning(`Event queue full for ${event.channelId}, discarding: ${event.text.substring(0, 50)}`);\n\t\t\treturn false;\n\t\t}\n\t\tlog.logInfo(`Enqueueing event for ${event.channelId}: ${event.text.substring(0, 50)}`);\n\t\tqueue.enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this, true);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// AI Card operations\n\t// ==========================================================================\n\n\t/**\n\t * Get or create an AI Card for a channel.\n\t */\n\tasync ensureCard(channelId: string): Promise<void> {\n\t\tif (!this.config.cardTemplateId) return;\n\t\tconst existing = this.activeCards.get(channelId);\n\t\tif (existing && !existing.finished) return;\n\t\tawait this.createCard(channelId);\n\t}\n\n\t/**\n\t * Stream content to the active AI Card for a channel.\n\t */\n\tasync streamToCard(channelId: string, content: string, finalize: boolean = false): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\tif (finalize) {\n\t\t\t\treturn this.sendPlain(channelId, content);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tconst streamed = await this.streamCard(card, content, finalize);\n\t\tif (!streamed) {\n\t\t\tthis.activeCards.delete(channelId);\n\t\t}\n\t\treturn streamed;\n\t}\n\n\t/**\n\t * Finalize the active card for a channel without falling back to a plain message.\n\t * Returns true if a card was finalized, false if no active card existed.\n\t */\n\tasync finalizeExistingCard(channelId: string, content: string): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst finalized = await this.streamCard(card, content, true);\n\t\tthis.activeCards.delete(channelId);\n\t\treturn finalized;\n\t}\n\n\t/**\n\t * Finalize and remove the active card for a channel.\n\t */\n\tasync finalizeCard(channelId: string, content: string): Promise<boolean> {\n\t\tconst finalized = await this.finalizeExistingCard(channelId, content);\n\t\tif (!finalized) {\n\t\t\treturn this.sendPlain(channelId, content);\n\t\t}\n\t\treturn true;\n\t}\n\n\tdiscardCard(channelId: string): void {\n\t\tthis.activeCards.delete(channelId);\n\t}\n\n\t/**\n\t * Send a normal message natively mapping DM and Group to correct endpoints (fallback when no card).\n\t */\n\tasync sendPlain(channelId: string, text: string): Promise<boolean> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return false;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot send plain message`);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\t\tconst isGroup = meta.conversationType === \"2\";\n\n\t\tconst hasMarkdown = /^#{1,6}\\s|^\\s*[-*]\\s|\\*\\*.*\\*\\*|```|`[^`]+`|\\[.*?\\]\\(.*?\\)/m.test(text);\n\n\t\tconst msgKey = hasMarkdown ? \"sampleMarkdown\" : \"sampleText\";\n\t\tconst msgParam = hasMarkdown ? JSON.stringify({ text, title: \"Bot\" }) : JSON.stringify({ content: text });\n\n\t\tconst url = isGroup\n\t\t\t? `${DINGTALK_API}/v1.0/robot/groupMessages/send`\n\t\t\t: `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;\n\n\t\tconst body: any = {\n\t\t\trobotCode,\n\t\t\tmsgKey,\n\t\t\tmsgParam,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.openConversationId = meta.conversationId;\n\t\t} else {\n\t\t\tbody.userIds = [meta.senderId];\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(url, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk plain send failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk plain send error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - AI Card implementation\n\t// ==========================================================================\n\n\tprivate async createCard(channelId: string): Promise<AICard | null> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return null;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot create card`);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isGroup = meta.conversationType === \"2\";\n\t\tconst instanceId = `card_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\n\t\t// openSpaceId format:\n\t\t// 群聊: dtv1.card//IM_GROUP.{openConversationId}\n\t\t// 单聊: dtv1.card//IM_ROBOT.{userId}\n\t\tconst openSpaceId = isGroup\n\t\t\t? `dtv1.card//IM_GROUP.${meta.conversationId}`\n\t\t\t: `dtv1.card//IM_ROBOT.${meta.senderId}`;\n\n\t\tconst body: Record<string, unknown> = {\n\t\t\tcardTemplateId: this.config.cardTemplateId,\n\t\t\toutTrackId: instanceId,\n\t\t\tcardData: { cardParamMap: {} },\n\t\t\tcallbackType: \"STREAM\",\n\t\t\timGroupOpenSpaceModel: { supportForward: true },\n\t\t\timRobotOpenSpaceModel: { supportForward: true },\n\t\t\topenSpaceId,\n\t\t\tuserIdType: 1,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.imGroupOpenDeliverModel = { robotCode };\n\t\t} else {\n\t\t\tbody.imRobotOpenDeliverModel = { spaceType: \"IM_ROBOT\" };\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(`${DINGTALK_API}/v1.0/card/instances/createAndDeliver`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk Card: create failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: create failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tconst card: AICard = {\n\t\t\tinstanceId,\n\t\t\tconversationId: meta.conversationId,\n\t\t\taccessToken: token,\n\t\t\ttemplateKey: this.config.cardTemplateKey || \"content\",\n\t\t\tcreatedAt: Date.now() / 1000,\n\t\t\tlastUpdated: Date.now() / 1000,\n\t\t\tcontent: \"\",\n\t\t\tfinished: false,\n\t\t};\n\t\tthis.activeCards.set(channelId, card);\n\t\treturn card;\n\t}\n\n\tprivate async streamCard(card: AICard, content: string, finalize: boolean = false): Promise<boolean> {\n\t\t// Refresh token if needed\n\t\tconst ageSecs = Date.now() / 1000 - card.createdAt;\n\t\tif (ageSecs > TOKEN_REFRESH_SECS) {\n\t\t\tconst token = await this.getAccessToken();\n\t\t\tif (token) {\n\t\t\t\tcard.accessToken = token;\n\t\t\t}\n\t\t}\n\n\t\tconst body = {\n\t\t\toutTrackId: card.instanceId,\n\t\t\tguid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n\t\t\tkey: card.templateKey,\n\t\t\tcontent,\n\t\t\tisFull: true,\n\t\t\tisFinalize: finalize,\n\t\t\tisError: false,\n\t\t};\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tawait axios.put(`${DINGTALK_API}/v1.0/card/streaming`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": card.accessToken,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst duration = Date.now() - start;\n\t\t\tif (duration > 1000) {\n\t\t\t\tlog.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);\n\t\t\t}\n\n\t\t\tcard.lastUpdated = Date.now() / 1000;\n\t\t\tcard.content = content;\n\t\t\tif (finalize) {\n\t\t\t\tcard.finished = true;\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk Card: streaming failed (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: streaming failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Access Token\n\t// ==========================================================================\n\n\tprivate async getAccessToken(): Promise<string | null> {\n\t\tif (this.accessToken && Date.now() / 1000 < this.tokenExpiry) {\n\t\t\treturn this.accessToken;\n\t\t}\n\n\t\ttry {\n\t\t\tconst resp = await axios.post(\n\t\t\t\t`${DINGTALK_API}/v1.0/oauth2/accessToken`,\n\t\t\t\t{\n\t\t\t\t\tappKey: this.config.clientId,\n\t\t\t\t\tappSecret: this.config.clientSecret,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst data = resp.data as { accessToken?: string; expireIn?: number };\n\t\t\tthis.accessToken = data.accessToken || null;\n\t\t\tthis.tokenExpiry = Date.now() / 1000 + (data.expireIn || 7200) - 60;\n\t\t\treturn this.accessToken;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk: failed to get access token (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to get access token\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Message handling\n\t// ==========================================================================\n\n\tprivate extractContent(data: RobotMessage): string {\n\t\t// 1. text 类型消息:从 text.content 提取\n\t\tconst textContent = (data.text?.content || \"\").trim();\n\t\tif (textContent) return textContent;\n\n\t\t// 2. richText 类型消息:从 content.richText 列表提取文本片段\n\t\tconst raw = data as unknown as Record<string, unknown>;\n\t\tconst contentObj = raw.content as { richText?: Array<Record<string, string>> } | undefined;\n\t\tif (contentObj?.richText) {\n\t\t\tconst parts: string[] = [];\n\t\t\tfor (const item of contentObj.richText) {\n\t\t\t\tif (item.text) parts.push(item.text);\n\t\t\t}\n\t\t\tconst joined = parts.join(\"\").trim();\n\t\t\tif (joined) return joined;\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\tprivate async onStreamMessage(data: RobotMessage): Promise<void> {\n\t\tconst content = this.extractContent(data);\n\t\tconst senderId = data.senderStaffId || data.senderId || \"\";\n\t\tconst senderName = data.senderNick || \"Unknown\";\n\t\tconst conversationId = data.conversationId || \"\";\n\t\tconst conversationType = data.conversationType || \"1\";\n\n\t\tif (!content) {\n\t\t\tconst msgtype = (data as unknown as Record<string, unknown>).msgtype || \"unknown\";\n\t\t\tlog.logWarning(`DingTalk: empty message (type=${msgtype})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.allowFrom && this.config.allowFrom.length > 0) {\n\t\t\tif (!this.config.allowFrom.includes(senderId)) {\n\t\t\t\tlog.logWarning(`DingTalk: ignoring message from unauthorized user ${senderName} (${senderId})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Determine channel ID\n\t\tconst channelId = conversationType === \"2\" ? `group_${conversationId}` : `dm_${senderId}`;\n\n\t\tlog.logInfo(`DingTalk ← ${senderName} (${senderId}) [${channelId}]: ${content.substring(0, 80)}`);\n\n\t\t// Cache conversation metadata for card creation\n\t\tthis.setConversationMeta(channelId, {\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t\tsenderId,\n\t\t});\n\n\t\t// Build event\n\t\tconst event: DingTalkEvent = {\n\t\t\ttype: conversationType === \"2\" ? \"group\" : \"dm\",\n\t\t\tchannelId,\n\t\t\tts: Date.now().toString(),\n\t\t\tuser: senderId,\n\t\t\tuserName: senderName,\n\t\t\ttext: content,\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t};\n\n\t\t// Check for stop command\n\t\tif (content.toLowerCase().trim() === \"stop\") {\n\t\t\tif (this.handler.isRunning(channelId)) {\n\t\t\t\tthis.handler.handleStop(channelId, this);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if busy\n\t\tif (this.handler.isRunning(channelId)) {\n\t\t\tconst busyMsg = \"正在处理中,请稍候。发送 `stop` 可取消当前任务。\";\n\t\t\tawait this.sendPlain(channelId, busyMsg);\n\t\t\treturn;\n\t\t}\n\n\t\t// Enqueue for processing\n\t\tthis.getQueue(channelId).enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getQueue(channelId: string): ChannelQueue {\n\t\tlet queue = this.queues.get(channelId);\n\t\tif (!queue) {\n\t\t\tqueue = new ChannelQueue();\n\t\t\tthis.queues.set(channelId, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate getConversationMeta(channelId: string): ConversationMeta | null {\n\t\tconst cached = this.convMeta.get(channelId);\n\t\tif (cached) return cached;\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath || !existsSync(metaPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf-8\")) as Partial<ConversationMeta>;\n\t\t\tif (!parsed.conversationId || !parsed.conversationType || !parsed.senderId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst meta: ConversationMeta = {\n\t\t\t\tconversationId: parsed.conversationId,\n\t\t\t\tconversationType: parsed.conversationType,\n\t\t\t\tsenderId: parsed.senderId,\n\t\t\t};\n\t\t\tthis.convMeta.set(channelId, meta);\n\t\t\treturn meta;\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to load conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate setConversationMeta(channelId: string, meta: ConversationMeta): void {\n\t\tthis.convMeta.set(channelId, meta);\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath) return;\n\n\t\ttry {\n\t\t\tmkdirSync(dirname(metaPath), { recursive: true });\n\t\t\twriteFileSync(metaPath, JSON.stringify(meta, null, 2), \"utf-8\");\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to persist conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate getConversationMetaPath(channelId: string): string | null {\n\t\tif (!this.config.stateDir) return null;\n\t\treturn join(this.config.stateDir, channelId, \".channel-meta.json\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dingtalk.d.ts","sourceRoot":"","sources":["../src/dingtalk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DingTalk communication layer using dingtalk-stream SDK with AI Card streaming.\n *\n * Handles:\n * - Receiving messages via DingTalk Stream Mode (DWClient)\n * - Responding via AI Card (streaming) or plain markdown (fallback)\n * - Access token management\n * - Per-channel message queuing\n */\nimport axios from \"axios\";\nimport { DWClient, type DWClientDownStream, type RobotMessage, TOPIC_ROBOT } from \"dingtalk-stream\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { parseBuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DingTalkConfig {\n\tclientId: string;\n\tclientSecret: string;\n\trobotCode?: string;\n\tcardTemplateId?: string;\n\tcardTemplateKey?: string;\n\tallowFrom?: string[];\n\tstateDir?: string;\n}\n\nexport interface DingTalkEvent {\n\ttype: \"dm\" | \"group\";\n\tchannelId: string; // dm_{staffId} or group_{conversationId}\n\tts: string;\n\tuser: string; // sender staff id\n\tuserName: string; // sender nickname\n\ttext: string;\n\tconversationId: string;\n\tconversationType: string; // \"1\" = DM, \"2\" = group\n}\n\nexport interface DingTalkContext {\n\tmessage: {\n\t\ttext: string;\n\t\trawText: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\tchannel: string;\n\t\tts: string;\n\t};\n\tchannelName?: string;\n\trespond: (text: string, shouldLog?: boolean) => Promise<void>;\n\trespondPlain: (text: string, shouldLog?: boolean) => Promise<boolean>;\n\treplaceMessage: (text: string) => Promise<void>;\n\trespondInThread: (text: string) => Promise<void>;\n\tsetTyping: (isTyping: boolean) => Promise<void>;\n\tsetWorking: (working: boolean) => Promise<void>;\n\tdeleteMessage: () => Promise<void>;\n\tflush: () => Promise<void>;\n\tclose: () => Promise<void>;\n}\n\nexport type BusyMessageMode = \"steer\" | \"followUp\";\n\nexport interface DingTalkHandler {\n\tisRunning(channelId: string): boolean;\n\thandleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;\n\thandleStop(channelId: string, bot: DingTalkBot): Promise<void>;\n\thandleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<void>;\n}\n\n// ============================================================================\n// AI Card State\n// ============================================================================\n\ninterface AICard {\n\tinstanceId: string;\n\tconversationId: string;\n\taccessToken: string;\n\ttemplateKey: string;\n\tcreatedAt: number;\n\tlastUpdated: number;\n\tcontent: string;\n\tfinished: boolean;\n}\n\ninterface ConversationMeta {\n\tconversationId: string;\n\tconversationType: string;\n\tsenderId: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n\tprivate queue: QueuedWork[] = [];\n\tprivate processing = false;\n\n\tenqueue(work: QueuedWork): void {\n\t\tthis.queue.push(work);\n\t\tthis.processNext();\n\t}\n\n\tsize(): number {\n\t\treturn this.queue.length;\n\t}\n\n\tprivate async processNext(): Promise<void> {\n\t\tif (this.processing || this.queue.length === 0) return;\n\t\tthis.processing = true;\n\t\tconst work = this.queue.shift()!;\n\t\ttry {\n\t\t\tawait work();\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Queue error\", err instanceof Error ? err.message : String(err));\n\t\t}\n\t\tthis.processing = false;\n\t\tthis.processNext();\n\t}\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DINGTALK_API = \"https://api.dingtalk.com\";\nconst TOKEN_REFRESH_SECS = 90 * 60; // 1.5 hours (tokens expire after 2 hours)\n\n// ============================================================================\n// DingTalkBot\n// ============================================================================\n\nexport class DingTalkBot {\n\tprivate handler: DingTalkHandler;\n\tprivate config: DingTalkConfig;\n\n\t// Access token cache\n\tprivate accessToken: string | null = null;\n\tprivate tokenExpiry = 0;\n\n\t// Active AI cards: channelId → AICard\n\tprivate activeCards = new Map<string, AICard>();\n\n\t// Conversation metadata cache: channelId → metadata\n\tprivate convMeta = new Map<string, ConversationMeta>();\n\n\t// Per-channel queues\n\tprivate queues = new Map<string, ChannelQueue>();\n\n\t// Connection stability\n\tprivate client: DWClient | null = null;\n\tprivate lastSocketAvailableTime = Date.now();\n\tprivate activeMessageProcessing = false;\n\tprivate keepAliveTimer: NodeJS.Timeout | null = null;\n\tprivate isReconnecting = false;\n\tprivate isStopped = false;\n\tprivate reconnectAttempts = 0;\n\n\t// Deduplication cache (Set for O(1) lookup, order array for FIFO eviction)\n\tprivate processedIds = new Set<string>();\n\tprivate processedIdsOrder: string[] = [];\n\n\tconstructor(handler: DingTalkHandler, config: DingTalkConfig) {\n\t\tthis.handler = handler;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Mark an ID as processed. Returns true if this is a new ID, false if already seen.\n\t * Maintains a FIFO buffer of at most 200 entries.\n\t */\n\tprivate markProcessed(id: string): boolean {\n\t\tif (this.processedIds.has(id)) return false;\n\t\tthis.processedIds.add(id);\n\t\tthis.processedIdsOrder.push(id);\n\t\twhile (this.processedIdsOrder.length > 200) {\n\t\t\tthis.processedIds.delete(this.processedIdsOrder.shift()!);\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// Public API\n\t// ==========================================================================\n\n\tasync start(): Promise<void> {\n\t\tif (!this.config.clientId || !this.config.clientSecret) {\n\t\t\tlog.logWarning(\"DingTalk: clientId / clientSecret not configured\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.cardTemplateId) {\n\t\t\tlog.logWarning(\"DingTalk: cardTemplateId not configured — AI Card streaming will not work\");\n\t\t}\n\n\t\tlog.logInfo(`DingTalk: initializing stream (clientId=${this.config.clientId.substring(0, 8)}…)`);\n\n\t\tif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\t\t\taxios.defaults.proxy = false;\n\t\t}\n\n\t\tthis.client = new DWClient({\n\t\t\tclientId: this.config.clientId,\n\t\t\tclientSecret: this.config.clientSecret,\n\t\t\tautoReconnect: false,\n\t\t\tkeepAlive: false,\n\t\t} as any);\n\n\t\tthis.client.registerCallbackListener(TOPIC_ROBOT, (msg: DWClientDownStream) => {\n\t\t\treturn this.handleRawMessage(msg);\n\t\t});\n\n\t\tlog.logConnected();\n\t\tawait this.doReconnect(true); // Initial connection\n\t}\n\n\tprivate handleRawMessage(msg: DWClientDownStream): { status: \"SUCCESS\"; message: string } {\n\t\t// 1. Immediate ACK\n\t\tif (msg.headers?.messageId && this.client) {\n\t\t\tthis.client.socketCallBackResponse(msg.headers.messageId, { status: \"SUCCESS\", message: \"OK\" });\n\t\t}\n\n\t\t// 2. Protocol deduplication\n\t\tconst messageId = msg.headers?.messageId;\n\t\tif (messageId && !this.markProcessed(messageId)) {\n\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t}\n\n\t\ttry {\n\t\t\tconst data: RobotMessage = typeof msg.data === \"string\" ? JSON.parse(msg.data) : msg.data;\n\n\t\t\t// 3. Business logic deduplication\n\t\t\tconst msgId = (data as any).msgId;\n\t\t\tif (msgId && !this.markProcessed(msgId)) {\n\t\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t\t}\n\n\t\t\t// Fire-and-forget processing\n\t\t\tthis.onStreamMessage(data).catch((err: unknown) => {\n\t\t\t\tlog.logWarning(\"DingTalk handler error\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"DingTalk: failed to parse message\", err instanceof Error ? err.message : String(err));\n\t\t}\n\n\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t}\n\n\tprivate async doReconnect(immediate = false) {\n\t\tif (this.isReconnecting || this.isStopped || !this.client) return;\n\t\tthis.isReconnecting = true;\n\t\tlet connectionFailed = false;\n\n\t\tif (!immediate && this.reconnectAttempts > 0) {\n\t\t\tconst delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);\n\t\t\tlog.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t}\n\n\t\ttry {\n\t\t\tconst socket = (this.client as any).socket;\n\t\t\tif (socket?.readyState === 1 || socket?.readyState === 3) {\n\t\t\t\tawait (this.client as any).disconnect();\n\t\t\t}\n\n\t\t\tawait this.client.connect();\n\n\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\tthis.reconnectAttempts = 0; // Success, reset backoff\n\t\t\tlog.logInfo(\"DingTalk: connected to stream.\");\n\n\t\t\t// Setup keep alive\n\t\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\t\tthis.keepAliveTimer = setInterval(() => {\n\t\t\t\tif (this.isStopped) return;\n\n\t\t\t\tconst elapsed = Date.now() - this.lastSocketAvailableTime;\n\t\t\t\tif (elapsed > 90 * 1000 && !this.activeMessageProcessing) {\n\t\t\t\t\tlog.logWarning(\"DingTalk: connection timeout detected (>90s). Keeping active where possible...\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst s = (this.client as any)?.socket;\n\t\t\t\t\tif (s?.readyState === 1) {\n\t\t\t\t\t\ts.ping();\n\t\t\t\t\t}\n\t\t\t\t} catch (_err) {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}, 30 * 1000);\n\n\t\t\t// Setup native socket events\n\t\t\tconst s = (this.client as any).socket;\n\n\t\t\ts?.on(\"pong\", () => {\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t});\n\n\t\t\ts?.on(\"close\", (code: number, reason: string) => {\n\t\t\t\tlog.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);\n\t\t\t\tif (this.isStopped) return;\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.doReconnect(true).catch((err) => {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: reconnect failed\", err instanceof Error ? err.message : String(err));\n\t\t\t\t\t});\n\t\t\t\t}, 1000);\n\t\t\t});\n\n\t\t\ts?.on(\"message\", (raw: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(raw);\n\t\t\t\t\tif (msg.type === \"SYSTEM\" && msg.headers?.topic === \"disconnect\") {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: disconnect event received from server.\");\n\t\t\t\t\t\tif (!this.isStopped) {\n\t\t\t\t\t\t\tthis.doReconnect(true).catch(() => {});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\t// skip\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthis.reconnectAttempts++;\n\t\t\tconnectionFailed = true;\n\t\t\tlog.logWarning(\"DingTalk: connection failed\", err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tthis.isReconnecting = false;\n\t\t}\n\n\t\t// Auto-retry on failure with exponential backoff\n\t\tif (connectionFailed && !this.isStopped) {\n\t\t\tthis.doReconnect().catch(() => {});\n\t\t}\n\t}\n\n\tstop() {\n\t\tthis.isStopped = true;\n\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\t(this.client as any).disconnect();\n\t\t\t} catch (_e) {}\n\t\t}\n\t}\n\n\t/**\n\t * Enqueue an event for processing.\n\t * Returns true if enqueued, false if queue is full (max 5).\n\t */\n\tenqueueEvent(event: DingTalkEvent): boolean {\n\t\tconst queue = this.getQueue(event.channelId);\n\t\tif (queue.size() >= 5) {\n\t\t\tlog.logWarning(`Event queue full for ${event.channelId}, discarding: ${event.text.substring(0, 50)}`);\n\t\t\treturn false;\n\t\t}\n\t\tlog.logInfo(`Enqueueing event for ${event.channelId}: ${event.text.substring(0, 50)}`);\n\t\tqueue.enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this, true);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// AI Card operations\n\t// ==========================================================================\n\n\t/**\n\t * Get or create an AI Card for a channel.\n\t */\n\tasync ensureCard(channelId: string): Promise<void> {\n\t\tif (!this.config.cardTemplateId) return;\n\t\tconst existing = this.activeCards.get(channelId);\n\t\tif (existing && !existing.finished) return;\n\t\tawait this.createCard(channelId);\n\t}\n\n\t/**\n\t * Stream content to the active AI Card for a channel.\n\t */\n\tasync streamToCard(channelId: string, content: string, finalize: boolean = false): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\tif (finalize) {\n\t\t\t\treturn this.sendPlain(channelId, content);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tconst streamed = await this.streamCard(card, content, finalize);\n\t\tif (!streamed) {\n\t\t\tthis.activeCards.delete(channelId);\n\t\t}\n\t\treturn streamed;\n\t}\n\n\t/**\n\t * Finalize the active card for a channel without falling back to a plain message.\n\t * Returns true if a card was finalized, false if no active card existed.\n\t */\n\tasync finalizeExistingCard(channelId: string, content: string): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst finalized = await this.streamCard(card, content, true);\n\t\tthis.activeCards.delete(channelId);\n\t\treturn finalized;\n\t}\n\n\t/**\n\t * Finalize and remove the active card for a channel.\n\t */\n\tasync finalizeCard(channelId: string, content: string): Promise<boolean> {\n\t\tconst finalized = await this.finalizeExistingCard(channelId, content);\n\t\tif (!finalized) {\n\t\t\treturn this.sendPlain(channelId, content);\n\t\t}\n\t\treturn true;\n\t}\n\n\tdiscardCard(channelId: string): void {\n\t\tthis.activeCards.delete(channelId);\n\t}\n\n\t/**\n\t * Send a normal message natively mapping DM and Group to correct endpoints (fallback when no card).\n\t */\n\tasync sendPlain(channelId: string, text: string): Promise<boolean> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return false;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot send plain message`);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\t\tconst isGroup = meta.conversationType === \"2\";\n\n\t\tconst hasMarkdown = /^#{1,6}\\s|^\\s*[-*]\\s|\\*\\*.*\\*\\*|```|`[^`]+`|\\[.*?\\]\\(.*?\\)/m.test(text);\n\n\t\tconst msgKey = hasMarkdown ? \"sampleMarkdown\" : \"sampleText\";\n\t\tconst msgParam = hasMarkdown ? JSON.stringify({ text, title: \"Bot\" }) : JSON.stringify({ content: text });\n\n\t\tconst url = isGroup\n\t\t\t? `${DINGTALK_API}/v1.0/robot/groupMessages/send`\n\t\t\t: `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;\n\n\t\tconst body: any = {\n\t\t\trobotCode,\n\t\t\tmsgKey,\n\t\t\tmsgParam,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.openConversationId = meta.conversationId;\n\t\t} else {\n\t\t\tbody.userIds = [meta.senderId];\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(url, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk plain send failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk plain send error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - AI Card implementation\n\t// ==========================================================================\n\n\tprivate async createCard(channelId: string): Promise<AICard | null> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return null;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot create card`);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isGroup = meta.conversationType === \"2\";\n\t\tconst instanceId = `card_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\n\t\t// openSpaceId format:\n\t\t// 群聊: dtv1.card//IM_GROUP.{openConversationId}\n\t\t// 单聊: dtv1.card//IM_ROBOT.{userId}\n\t\tconst openSpaceId = isGroup\n\t\t\t? `dtv1.card//IM_GROUP.${meta.conversationId}`\n\t\t\t: `dtv1.card//IM_ROBOT.${meta.senderId}`;\n\n\t\tconst body: Record<string, unknown> = {\n\t\t\tcardTemplateId: this.config.cardTemplateId,\n\t\t\toutTrackId: instanceId,\n\t\t\tcardData: { cardParamMap: {} },\n\t\t\tcallbackType: \"STREAM\",\n\t\t\timGroupOpenSpaceModel: { supportForward: true },\n\t\t\timRobotOpenSpaceModel: { supportForward: true },\n\t\t\topenSpaceId,\n\t\t\tuserIdType: 1,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.imGroupOpenDeliverModel = { robotCode };\n\t\t} else {\n\t\t\tbody.imRobotOpenDeliverModel = { spaceType: \"IM_ROBOT\" };\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(`${DINGTALK_API}/v1.0/card/instances/createAndDeliver`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk Card: create failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: create failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tconst card: AICard = {\n\t\t\tinstanceId,\n\t\t\tconversationId: meta.conversationId,\n\t\t\taccessToken: token,\n\t\t\ttemplateKey: this.config.cardTemplateKey || \"content\",\n\t\t\tcreatedAt: Date.now() / 1000,\n\t\t\tlastUpdated: Date.now() / 1000,\n\t\t\tcontent: \"\",\n\t\t\tfinished: false,\n\t\t};\n\t\tthis.activeCards.set(channelId, card);\n\t\treturn card;\n\t}\n\n\tprivate async streamCard(card: AICard, content: string, finalize: boolean = false): Promise<boolean> {\n\t\t// Refresh token if needed\n\t\tconst ageSecs = Date.now() / 1000 - card.createdAt;\n\t\tif (ageSecs > TOKEN_REFRESH_SECS) {\n\t\t\tconst token = await this.getAccessToken();\n\t\t\tif (token) {\n\t\t\t\tcard.accessToken = token;\n\t\t\t}\n\t\t}\n\n\t\tconst body = {\n\t\t\toutTrackId: card.instanceId,\n\t\t\tguid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n\t\t\tkey: card.templateKey,\n\t\t\tcontent,\n\t\t\tisFull: true,\n\t\t\tisFinalize: finalize,\n\t\t\tisError: false,\n\t\t};\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tawait axios.put(`${DINGTALK_API}/v1.0/card/streaming`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": card.accessToken,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst duration = Date.now() - start;\n\t\t\tif (duration > 1000) {\n\t\t\t\tlog.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);\n\t\t\t}\n\n\t\t\tcard.lastUpdated = Date.now() / 1000;\n\t\t\tcard.content = content;\n\t\t\tif (finalize) {\n\t\t\t\tcard.finished = true;\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk Card: streaming failed (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: streaming failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Access Token\n\t// ==========================================================================\n\n\tprivate async getAccessToken(): Promise<string | null> {\n\t\tif (this.accessToken && Date.now() / 1000 < this.tokenExpiry) {\n\t\t\treturn this.accessToken;\n\t\t}\n\n\t\ttry {\n\t\t\tconst resp = await axios.post(\n\t\t\t\t`${DINGTALK_API}/v1.0/oauth2/accessToken`,\n\t\t\t\t{\n\t\t\t\t\tappKey: this.config.clientId,\n\t\t\t\t\tappSecret: this.config.clientSecret,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst data = resp.data as { accessToken?: string; expireIn?: number };\n\t\t\tthis.accessToken = data.accessToken || null;\n\t\t\tthis.tokenExpiry = Date.now() / 1000 + (data.expireIn || 7200) - 60;\n\t\t\treturn this.accessToken;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk: failed to get access token (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to get access token\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Message handling\n\t// ==========================================================================\n\n\tprivate extractContent(data: RobotMessage): string {\n\t\t// 1. text 类型消息:从 text.content 提取\n\t\tconst textContent = (data.text?.content || \"\").trim();\n\t\tif (textContent) return textContent;\n\n\t\t// 2. richText 类型消息:从 content.richText 列表提取文本片段\n\t\tconst raw = data as unknown as Record<string, unknown>;\n\t\tconst contentObj = raw.content as { richText?: Array<Record<string, string>> } | undefined;\n\t\tif (contentObj?.richText) {\n\t\t\tconst parts: string[] = [];\n\t\t\tfor (const item of contentObj.richText) {\n\t\t\t\tif (item.text) parts.push(item.text);\n\t\t\t}\n\t\t\tconst joined = parts.join(\"\").trim();\n\t\t\tif (joined) return joined;\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\tprivate async onStreamMessage(data: RobotMessage): Promise<void> {\n\t\tconst content = this.extractContent(data);\n\t\tconst senderId = data.senderStaffId || data.senderId || \"\";\n\t\tconst senderName = data.senderNick || \"Unknown\";\n\t\tconst conversationId = data.conversationId || \"\";\n\t\tconst conversationType = data.conversationType || \"1\";\n\n\t\tif (!content) {\n\t\t\tconst msgtype = (data as unknown as Record<string, unknown>).msgtype || \"unknown\";\n\t\t\tlog.logWarning(`DingTalk: empty message (type=${msgtype})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.allowFrom && this.config.allowFrom.length > 0) {\n\t\t\tif (!this.config.allowFrom.includes(senderId)) {\n\t\t\t\tlog.logWarning(`DingTalk: ignoring message from unauthorized user ${senderName} (${senderId})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Determine channel ID\n\t\tconst channelId = conversationType === \"2\" ? `group_${conversationId}` : `dm_${senderId}`;\n\n\t\tlog.logInfo(`DingTalk ← ${senderName} (${senderId}) [${channelId}]: ${content.substring(0, 80)}`);\n\n\t\t// Cache conversation metadata for card creation\n\t\tthis.setConversationMeta(channelId, {\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t\tsenderId,\n\t\t});\n\n\t\t// Build event\n\t\tconst event: DingTalkEvent = {\n\t\t\ttype: conversationType === \"2\" ? \"group\" : \"dm\",\n\t\t\tchannelId,\n\t\t\tts: Date.now().toString(),\n\t\t\tuser: senderId,\n\t\t\tuserName: senderName,\n\t\t\ttext: content,\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t};\n\n\t\tconst builtInCommand = parseBuiltInCommand(content);\n\n\t\t// Check if busy\n\t\tif (this.handler.isRunning(channelId)) {\n\t\t\tif (builtInCommand?.name === \"help\") {\n\t\t\t\tawait this.sendPlain(channelId, renderBuiltInHelp());\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"stop\") {\n\t\t\t\tawait this.handler.handleStop(channelId, this);\n\t\t\t\tawait this.sendPlain(channelId, \"Stopping the current task.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"steer\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"followup\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"followUp\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tawait this.sendPlain(\n\t\t\t\t\tchannelId,\n\t\t\t\t\t\"A task is already running. Use `/stop`, `/steer <message>`, or `/followup <message>`. Plain messages default to steer.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", content);\n\t\t\treturn;\n\t\t}\n\n\t\t// Enqueue for processing\n\t\tthis.getQueue(channelId).enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getQueue(channelId: string): ChannelQueue {\n\t\tlet queue = this.queues.get(channelId);\n\t\tif (!queue) {\n\t\t\tqueue = new ChannelQueue();\n\t\t\tthis.queues.set(channelId, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate getConversationMeta(channelId: string): ConversationMeta | null {\n\t\tconst cached = this.convMeta.get(channelId);\n\t\tif (cached) return cached;\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath || !existsSync(metaPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf-8\")) as Partial<ConversationMeta>;\n\t\t\tif (!parsed.conversationId || !parsed.conversationType || !parsed.senderId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst meta: ConversationMeta = {\n\t\t\t\tconversationId: parsed.conversationId,\n\t\t\t\tconversationType: parsed.conversationType,\n\t\t\t\tsenderId: parsed.senderId,\n\t\t\t};\n\t\t\tthis.convMeta.set(channelId, meta);\n\t\t\treturn meta;\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to load conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate setConversationMeta(channelId: string, meta: ConversationMeta): void {\n\t\tthis.convMeta.set(channelId, meta);\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath) return;\n\n\t\ttry {\n\t\t\tmkdirSync(dirname(metaPath), { recursive: true });\n\t\t\twriteFileSync(metaPath, JSON.stringify(meta, null, 2), \"utf-8\");\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to persist conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate getConversationMetaPath(channelId: string): string | null {\n\t\tif (!this.config.stateDir) return null;\n\t\treturn join(this.config.stateDir, channelId, \".channel-meta.json\");\n\t}\n}\n"]}
|
package/dist/dingtalk.js
CHANGED
|
@@ -11,6 +11,7 @@ import axios from "axios";
|
|
|
11
11
|
import { DWClient, TOPIC_ROBOT } from "dingtalk-stream";
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
|
+
import { parseBuiltInCommand, renderBuiltInHelp } from "./commands.js";
|
|
14
15
|
import * as log from "./log.js";
|
|
15
16
|
class ChannelQueue {
|
|
16
17
|
queue = [];
|
|
@@ -574,17 +575,31 @@ export class DingTalkBot {
|
|
|
574
575
|
conversationId,
|
|
575
576
|
conversationType,
|
|
576
577
|
};
|
|
577
|
-
|
|
578
|
-
if (content.toLowerCase().trim() === "stop") {
|
|
579
|
-
if (this.handler.isRunning(channelId)) {
|
|
580
|
-
this.handler.handleStop(channelId, this);
|
|
581
|
-
}
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
578
|
+
const builtInCommand = parseBuiltInCommand(content);
|
|
584
579
|
// Check if busy
|
|
585
580
|
if (this.handler.isRunning(channelId)) {
|
|
586
|
-
|
|
587
|
-
|
|
581
|
+
if (builtInCommand?.name === "help") {
|
|
582
|
+
await this.sendPlain(channelId, renderBuiltInHelp());
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (builtInCommand?.name === "stop") {
|
|
586
|
+
await this.handler.handleStop(channelId, this);
|
|
587
|
+
await this.sendPlain(channelId, "Stopping the current task.");
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (builtInCommand?.name === "steer") {
|
|
591
|
+
await this.handler.handleBusyMessage(event, this, "steer", builtInCommand.args);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (builtInCommand?.name === "followup") {
|
|
595
|
+
await this.handler.handleBusyMessage(event, this, "followUp", builtInCommand.args);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (builtInCommand) {
|
|
599
|
+
await this.sendPlain(channelId, "A task is already running. Use `/stop`, `/steer <message>`, or `/followup <message>`. Plain messages default to steer.");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
await this.handler.handleBusyMessage(event, this, "steer", content);
|
|
588
603
|
return;
|
|
589
604
|
}
|
|
590
605
|
// Enqueue for processing
|
package/dist/dingtalk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../src/dingtalk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DingTalk communication layer using dingtalk-stream SDK with AI Card streaming.\n *\n * Handles:\n * - Receiving messages via DingTalk Stream Mode (DWClient)\n * - Responding via AI Card (streaming) or plain markdown (fallback)\n * - Access token management\n * - Per-channel message queuing\n */\nimport axios from \"axios\";\nimport { DWClient, type DWClientDownStream, type RobotMessage, TOPIC_ROBOT } from \"dingtalk-stream\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DingTalkConfig {\n\tclientId: string;\n\tclientSecret: string;\n\trobotCode?: string;\n\tcardTemplateId?: string;\n\tcardTemplateKey?: string;\n\tallowFrom?: string[];\n\tstateDir?: string;\n}\n\nexport interface DingTalkEvent {\n\ttype: \"dm\" | \"group\";\n\tchannelId: string; // dm_{staffId} or group_{conversationId}\n\tts: string;\n\tuser: string; // sender staff id\n\tuserName: string; // sender nickname\n\ttext: string;\n\tconversationId: string;\n\tconversationType: string; // \"1\" = DM, \"2\" = group\n}\n\nexport interface DingTalkContext {\n\tmessage: {\n\t\ttext: string;\n\t\trawText: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\tchannel: string;\n\t\tts: string;\n\t};\n\tchannelName?: string;\n\trespond: (text: string, shouldLog?: boolean) => Promise<void>;\n\trespondPlain: (text: string, shouldLog?: boolean) => Promise<boolean>;\n\treplaceMessage: (text: string) => Promise<void>;\n\trespondInThread: (text: string) => Promise<void>;\n\tsetTyping: (isTyping: boolean) => Promise<void>;\n\tsetWorking: (working: boolean) => Promise<void>;\n\tdeleteMessage: () => Promise<void>;\n\tflush: () => Promise<void>;\n\tclose: () => Promise<void>;\n}\n\nexport interface DingTalkHandler {\n\tisRunning(channelId: string): boolean;\n\thandleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;\n\thandleStop(channelId: string, bot: DingTalkBot): Promise<void>;\n}\n\n// ============================================================================\n// AI Card State\n// ============================================================================\n\ninterface AICard {\n\tinstanceId: string;\n\tconversationId: string;\n\taccessToken: string;\n\ttemplateKey: string;\n\tcreatedAt: number;\n\tlastUpdated: number;\n\tcontent: string;\n\tfinished: boolean;\n}\n\ninterface ConversationMeta {\n\tconversationId: string;\n\tconversationType: string;\n\tsenderId: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n\tprivate queue: QueuedWork[] = [];\n\tprivate processing = false;\n\n\tenqueue(work: QueuedWork): void {\n\t\tthis.queue.push(work);\n\t\tthis.processNext();\n\t}\n\n\tsize(): number {\n\t\treturn this.queue.length;\n\t}\n\n\tprivate async processNext(): Promise<void> {\n\t\tif (this.processing || this.queue.length === 0) return;\n\t\tthis.processing = true;\n\t\tconst work = this.queue.shift()!;\n\t\ttry {\n\t\t\tawait work();\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Queue error\", err instanceof Error ? err.message : String(err));\n\t\t}\n\t\tthis.processing = false;\n\t\tthis.processNext();\n\t}\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DINGTALK_API = \"https://api.dingtalk.com\";\nconst TOKEN_REFRESH_SECS = 90 * 60; // 1.5 hours (tokens expire after 2 hours)\n\n// ============================================================================\n// DingTalkBot\n// ============================================================================\n\nexport class DingTalkBot {\n\tprivate handler: DingTalkHandler;\n\tprivate config: DingTalkConfig;\n\n\t// Access token cache\n\tprivate accessToken: string | null = null;\n\tprivate tokenExpiry = 0;\n\n\t// Active AI cards: channelId → AICard\n\tprivate activeCards = new Map<string, AICard>();\n\n\t// Conversation metadata cache: channelId → metadata\n\tprivate convMeta = new Map<string, ConversationMeta>();\n\n\t// Per-channel queues\n\tprivate queues = new Map<string, ChannelQueue>();\n\n\t// Connection stability\n\tprivate client: DWClient | null = null;\n\tprivate lastSocketAvailableTime = Date.now();\n\tprivate activeMessageProcessing = false;\n\tprivate keepAliveTimer: NodeJS.Timeout | null = null;\n\tprivate isReconnecting = false;\n\tprivate isStopped = false;\n\tprivate reconnectAttempts = 0;\n\n\t// Deduplication cache (Set for O(1) lookup, order array for FIFO eviction)\n\tprivate processedIds = new Set<string>();\n\tprivate processedIdsOrder: string[] = [];\n\n\tconstructor(handler: DingTalkHandler, config: DingTalkConfig) {\n\t\tthis.handler = handler;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Mark an ID as processed. Returns true if this is a new ID, false if already seen.\n\t * Maintains a FIFO buffer of at most 200 entries.\n\t */\n\tprivate markProcessed(id: string): boolean {\n\t\tif (this.processedIds.has(id)) return false;\n\t\tthis.processedIds.add(id);\n\t\tthis.processedIdsOrder.push(id);\n\t\twhile (this.processedIdsOrder.length > 200) {\n\t\t\tthis.processedIds.delete(this.processedIdsOrder.shift()!);\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// Public API\n\t// ==========================================================================\n\n\tasync start(): Promise<void> {\n\t\tif (!this.config.clientId || !this.config.clientSecret) {\n\t\t\tlog.logWarning(\"DingTalk: clientId / clientSecret not configured\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.cardTemplateId) {\n\t\t\tlog.logWarning(\"DingTalk: cardTemplateId not configured — AI Card streaming will not work\");\n\t\t}\n\n\t\tlog.logInfo(`DingTalk: initializing stream (clientId=${this.config.clientId.substring(0, 8)}…)`);\n\n\t\tif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\t\t\taxios.defaults.proxy = false;\n\t\t}\n\n\t\tthis.client = new DWClient({\n\t\t\tclientId: this.config.clientId,\n\t\t\tclientSecret: this.config.clientSecret,\n\t\t\tautoReconnect: false,\n\t\t\tkeepAlive: false,\n\t\t} as any);\n\n\t\tthis.client.registerCallbackListener(TOPIC_ROBOT, (msg: DWClientDownStream) => {\n\t\t\treturn this.handleRawMessage(msg);\n\t\t});\n\n\t\tlog.logConnected();\n\t\tawait this.doReconnect(true); // Initial connection\n\t}\n\n\tprivate handleRawMessage(msg: DWClientDownStream): { status: \"SUCCESS\"; message: string } {\n\t\t// 1. Immediate ACK\n\t\tif (msg.headers?.messageId && this.client) {\n\t\t\tthis.client.socketCallBackResponse(msg.headers.messageId, { status: \"SUCCESS\", message: \"OK\" });\n\t\t}\n\n\t\t// 2. Protocol deduplication\n\t\tconst messageId = msg.headers?.messageId;\n\t\tif (messageId && !this.markProcessed(messageId)) {\n\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t}\n\n\t\ttry {\n\t\t\tconst data: RobotMessage = typeof msg.data === \"string\" ? JSON.parse(msg.data) : msg.data;\n\n\t\t\t// 3. Business logic deduplication\n\t\t\tconst msgId = (data as any).msgId;\n\t\t\tif (msgId && !this.markProcessed(msgId)) {\n\t\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t\t}\n\n\t\t\t// Fire-and-forget processing\n\t\t\tthis.onStreamMessage(data).catch((err: unknown) => {\n\t\t\t\tlog.logWarning(\"DingTalk handler error\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"DingTalk: failed to parse message\", err instanceof Error ? err.message : String(err));\n\t\t}\n\n\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t}\n\n\tprivate async doReconnect(immediate = false) {\n\t\tif (this.isReconnecting || this.isStopped || !this.client) return;\n\t\tthis.isReconnecting = true;\n\t\tlet connectionFailed = false;\n\n\t\tif (!immediate && this.reconnectAttempts > 0) {\n\t\t\tconst delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);\n\t\t\tlog.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t}\n\n\t\ttry {\n\t\t\tconst socket = (this.client as any).socket;\n\t\t\tif (socket?.readyState === 1 || socket?.readyState === 3) {\n\t\t\t\tawait (this.client as any).disconnect();\n\t\t\t}\n\n\t\t\tawait this.client.connect();\n\n\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\tthis.reconnectAttempts = 0; // Success, reset backoff\n\t\t\tlog.logInfo(\"DingTalk: connected to stream.\");\n\n\t\t\t// Setup keep alive\n\t\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\t\tthis.keepAliveTimer = setInterval(() => {\n\t\t\t\tif (this.isStopped) return;\n\n\t\t\t\tconst elapsed = Date.now() - this.lastSocketAvailableTime;\n\t\t\t\tif (elapsed > 90 * 1000 && !this.activeMessageProcessing) {\n\t\t\t\t\tlog.logWarning(\"DingTalk: connection timeout detected (>90s). Keeping active where possible...\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst s = (this.client as any)?.socket;\n\t\t\t\t\tif (s?.readyState === 1) {\n\t\t\t\t\t\ts.ping();\n\t\t\t\t\t}\n\t\t\t\t} catch (_err) {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}, 30 * 1000);\n\n\t\t\t// Setup native socket events\n\t\t\tconst s = (this.client as any).socket;\n\n\t\t\ts?.on(\"pong\", () => {\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t});\n\n\t\t\ts?.on(\"close\", (code: number, reason: string) => {\n\t\t\t\tlog.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);\n\t\t\t\tif (this.isStopped) return;\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.doReconnect(true).catch((err) => {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: reconnect failed\", err instanceof Error ? err.message : String(err));\n\t\t\t\t\t});\n\t\t\t\t}, 1000);\n\t\t\t});\n\n\t\t\ts?.on(\"message\", (raw: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(raw);\n\t\t\t\t\tif (msg.type === \"SYSTEM\" && msg.headers?.topic === \"disconnect\") {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: disconnect event received from server.\");\n\t\t\t\t\t\tif (!this.isStopped) {\n\t\t\t\t\t\t\tthis.doReconnect(true).catch(() => {});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\t// skip\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthis.reconnectAttempts++;\n\t\t\tconnectionFailed = true;\n\t\t\tlog.logWarning(\"DingTalk: connection failed\", err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tthis.isReconnecting = false;\n\t\t}\n\n\t\t// Auto-retry on failure with exponential backoff\n\t\tif (connectionFailed && !this.isStopped) {\n\t\t\tthis.doReconnect().catch(() => {});\n\t\t}\n\t}\n\n\tstop() {\n\t\tthis.isStopped = true;\n\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\t(this.client as any).disconnect();\n\t\t\t} catch (_e) {}\n\t\t}\n\t}\n\n\t/**\n\t * Enqueue an event for processing.\n\t * Returns true if enqueued, false if queue is full (max 5).\n\t */\n\tenqueueEvent(event: DingTalkEvent): boolean {\n\t\tconst queue = this.getQueue(event.channelId);\n\t\tif (queue.size() >= 5) {\n\t\t\tlog.logWarning(`Event queue full for ${event.channelId}, discarding: ${event.text.substring(0, 50)}`);\n\t\t\treturn false;\n\t\t}\n\t\tlog.logInfo(`Enqueueing event for ${event.channelId}: ${event.text.substring(0, 50)}`);\n\t\tqueue.enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this, true);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// AI Card operations\n\t// ==========================================================================\n\n\t/**\n\t * Get or create an AI Card for a channel.\n\t */\n\tasync ensureCard(channelId: string): Promise<void> {\n\t\tif (!this.config.cardTemplateId) return;\n\t\tconst existing = this.activeCards.get(channelId);\n\t\tif (existing && !existing.finished) return;\n\t\tawait this.createCard(channelId);\n\t}\n\n\t/**\n\t * Stream content to the active AI Card for a channel.\n\t */\n\tasync streamToCard(channelId: string, content: string, finalize: boolean = false): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\tif (finalize) {\n\t\t\t\treturn this.sendPlain(channelId, content);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tconst streamed = await this.streamCard(card, content, finalize);\n\t\tif (!streamed) {\n\t\t\tthis.activeCards.delete(channelId);\n\t\t}\n\t\treturn streamed;\n\t}\n\n\t/**\n\t * Finalize the active card for a channel without falling back to a plain message.\n\t * Returns true if a card was finalized, false if no active card existed.\n\t */\n\tasync finalizeExistingCard(channelId: string, content: string): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst finalized = await this.streamCard(card, content, true);\n\t\tthis.activeCards.delete(channelId);\n\t\treturn finalized;\n\t}\n\n\t/**\n\t * Finalize and remove the active card for a channel.\n\t */\n\tasync finalizeCard(channelId: string, content: string): Promise<boolean> {\n\t\tconst finalized = await this.finalizeExistingCard(channelId, content);\n\t\tif (!finalized) {\n\t\t\treturn this.sendPlain(channelId, content);\n\t\t}\n\t\treturn true;\n\t}\n\n\tdiscardCard(channelId: string): void {\n\t\tthis.activeCards.delete(channelId);\n\t}\n\n\t/**\n\t * Send a normal message natively mapping DM and Group to correct endpoints (fallback when no card).\n\t */\n\tasync sendPlain(channelId: string, text: string): Promise<boolean> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return false;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot send plain message`);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\t\tconst isGroup = meta.conversationType === \"2\";\n\n\t\tconst hasMarkdown = /^#{1,6}\\s|^\\s*[-*]\\s|\\*\\*.*\\*\\*|```|`[^`]+`|\\[.*?\\]\\(.*?\\)/m.test(text);\n\n\t\tconst msgKey = hasMarkdown ? \"sampleMarkdown\" : \"sampleText\";\n\t\tconst msgParam = hasMarkdown ? JSON.stringify({ text, title: \"Bot\" }) : JSON.stringify({ content: text });\n\n\t\tconst url = isGroup\n\t\t\t? `${DINGTALK_API}/v1.0/robot/groupMessages/send`\n\t\t\t: `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;\n\n\t\tconst body: any = {\n\t\t\trobotCode,\n\t\t\tmsgKey,\n\t\t\tmsgParam,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.openConversationId = meta.conversationId;\n\t\t} else {\n\t\t\tbody.userIds = [meta.senderId];\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(url, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk plain send failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk plain send error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - AI Card implementation\n\t// ==========================================================================\n\n\tprivate async createCard(channelId: string): Promise<AICard | null> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return null;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot create card`);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isGroup = meta.conversationType === \"2\";\n\t\tconst instanceId = `card_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\n\t\t// openSpaceId format:\n\t\t// 群聊: dtv1.card//IM_GROUP.{openConversationId}\n\t\t// 单聊: dtv1.card//IM_ROBOT.{userId}\n\t\tconst openSpaceId = isGroup\n\t\t\t? `dtv1.card//IM_GROUP.${meta.conversationId}`\n\t\t\t: `dtv1.card//IM_ROBOT.${meta.senderId}`;\n\n\t\tconst body: Record<string, unknown> = {\n\t\t\tcardTemplateId: this.config.cardTemplateId,\n\t\t\toutTrackId: instanceId,\n\t\t\tcardData: { cardParamMap: {} },\n\t\t\tcallbackType: \"STREAM\",\n\t\t\timGroupOpenSpaceModel: { supportForward: true },\n\t\t\timRobotOpenSpaceModel: { supportForward: true },\n\t\t\topenSpaceId,\n\t\t\tuserIdType: 1,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.imGroupOpenDeliverModel = { robotCode };\n\t\t} else {\n\t\t\tbody.imRobotOpenDeliverModel = { spaceType: \"IM_ROBOT\" };\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(`${DINGTALK_API}/v1.0/card/instances/createAndDeliver`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk Card: create failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: create failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tconst card: AICard = {\n\t\t\tinstanceId,\n\t\t\tconversationId: meta.conversationId,\n\t\t\taccessToken: token,\n\t\t\ttemplateKey: this.config.cardTemplateKey || \"content\",\n\t\t\tcreatedAt: Date.now() / 1000,\n\t\t\tlastUpdated: Date.now() / 1000,\n\t\t\tcontent: \"\",\n\t\t\tfinished: false,\n\t\t};\n\t\tthis.activeCards.set(channelId, card);\n\t\treturn card;\n\t}\n\n\tprivate async streamCard(card: AICard, content: string, finalize: boolean = false): Promise<boolean> {\n\t\t// Refresh token if needed\n\t\tconst ageSecs = Date.now() / 1000 - card.createdAt;\n\t\tif (ageSecs > TOKEN_REFRESH_SECS) {\n\t\t\tconst token = await this.getAccessToken();\n\t\t\tif (token) {\n\t\t\t\tcard.accessToken = token;\n\t\t\t}\n\t\t}\n\n\t\tconst body = {\n\t\t\toutTrackId: card.instanceId,\n\t\t\tguid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n\t\t\tkey: card.templateKey,\n\t\t\tcontent,\n\t\t\tisFull: true,\n\t\t\tisFinalize: finalize,\n\t\t\tisError: false,\n\t\t};\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tawait axios.put(`${DINGTALK_API}/v1.0/card/streaming`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": card.accessToken,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst duration = Date.now() - start;\n\t\t\tif (duration > 1000) {\n\t\t\t\tlog.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);\n\t\t\t}\n\n\t\t\tcard.lastUpdated = Date.now() / 1000;\n\t\t\tcard.content = content;\n\t\t\tif (finalize) {\n\t\t\t\tcard.finished = true;\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk Card: streaming failed (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: streaming failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Access Token\n\t// ==========================================================================\n\n\tprivate async getAccessToken(): Promise<string | null> {\n\t\tif (this.accessToken && Date.now() / 1000 < this.tokenExpiry) {\n\t\t\treturn this.accessToken;\n\t\t}\n\n\t\ttry {\n\t\t\tconst resp = await axios.post(\n\t\t\t\t`${DINGTALK_API}/v1.0/oauth2/accessToken`,\n\t\t\t\t{\n\t\t\t\t\tappKey: this.config.clientId,\n\t\t\t\t\tappSecret: this.config.clientSecret,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst data = resp.data as { accessToken?: string; expireIn?: number };\n\t\t\tthis.accessToken = data.accessToken || null;\n\t\t\tthis.tokenExpiry = Date.now() / 1000 + (data.expireIn || 7200) - 60;\n\t\t\treturn this.accessToken;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk: failed to get access token (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to get access token\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Message handling\n\t// ==========================================================================\n\n\tprivate extractContent(data: RobotMessage): string {\n\t\t// 1. text 类型消息:从 text.content 提取\n\t\tconst textContent = (data.text?.content || \"\").trim();\n\t\tif (textContent) return textContent;\n\n\t\t// 2. richText 类型消息:从 content.richText 列表提取文本片段\n\t\tconst raw = data as unknown as Record<string, unknown>;\n\t\tconst contentObj = raw.content as { richText?: Array<Record<string, string>> } | undefined;\n\t\tif (contentObj?.richText) {\n\t\t\tconst parts: string[] = [];\n\t\t\tfor (const item of contentObj.richText) {\n\t\t\t\tif (item.text) parts.push(item.text);\n\t\t\t}\n\t\t\tconst joined = parts.join(\"\").trim();\n\t\t\tif (joined) return joined;\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\tprivate async onStreamMessage(data: RobotMessage): Promise<void> {\n\t\tconst content = this.extractContent(data);\n\t\tconst senderId = data.senderStaffId || data.senderId || \"\";\n\t\tconst senderName = data.senderNick || \"Unknown\";\n\t\tconst conversationId = data.conversationId || \"\";\n\t\tconst conversationType = data.conversationType || \"1\";\n\n\t\tif (!content) {\n\t\t\tconst msgtype = (data as unknown as Record<string, unknown>).msgtype || \"unknown\";\n\t\t\tlog.logWarning(`DingTalk: empty message (type=${msgtype})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.allowFrom && this.config.allowFrom.length > 0) {\n\t\t\tif (!this.config.allowFrom.includes(senderId)) {\n\t\t\t\tlog.logWarning(`DingTalk: ignoring message from unauthorized user ${senderName} (${senderId})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Determine channel ID\n\t\tconst channelId = conversationType === \"2\" ? `group_${conversationId}` : `dm_${senderId}`;\n\n\t\tlog.logInfo(`DingTalk ← ${senderName} (${senderId}) [${channelId}]: ${content.substring(0, 80)}`);\n\n\t\t// Cache conversation metadata for card creation\n\t\tthis.setConversationMeta(channelId, {\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t\tsenderId,\n\t\t});\n\n\t\t// Build event\n\t\tconst event: DingTalkEvent = {\n\t\t\ttype: conversationType === \"2\" ? \"group\" : \"dm\",\n\t\t\tchannelId,\n\t\t\tts: Date.now().toString(),\n\t\t\tuser: senderId,\n\t\t\tuserName: senderName,\n\t\t\ttext: content,\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t};\n\n\t\t// Check for stop command\n\t\tif (content.toLowerCase().trim() === \"stop\") {\n\t\t\tif (this.handler.isRunning(channelId)) {\n\t\t\t\tthis.handler.handleStop(channelId, this);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if busy\n\t\tif (this.handler.isRunning(channelId)) {\n\t\t\tconst busyMsg = \"正在处理中,请稍候。发送 `stop` 可取消当前任务。\";\n\t\t\tawait this.sendPlain(channelId, busyMsg);\n\t\t\treturn;\n\t\t}\n\n\t\t// Enqueue for processing\n\t\tthis.getQueue(channelId).enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getQueue(channelId: string): ChannelQueue {\n\t\tlet queue = this.queues.get(channelId);\n\t\tif (!queue) {\n\t\t\tqueue = new ChannelQueue();\n\t\t\tthis.queues.set(channelId, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate getConversationMeta(channelId: string): ConversationMeta | null {\n\t\tconst cached = this.convMeta.get(channelId);\n\t\tif (cached) return cached;\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath || !existsSync(metaPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf-8\")) as Partial<ConversationMeta>;\n\t\t\tif (!parsed.conversationId || !parsed.conversationType || !parsed.senderId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst meta: ConversationMeta = {\n\t\t\t\tconversationId: parsed.conversationId,\n\t\t\t\tconversationType: parsed.conversationType,\n\t\t\t\tsenderId: parsed.senderId,\n\t\t\t};\n\t\t\tthis.convMeta.set(channelId, meta);\n\t\t\treturn meta;\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to load conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate setConversationMeta(channelId: string, meta: ConversationMeta): void {\n\t\tthis.convMeta.set(channelId, meta);\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath) return;\n\n\t\ttry {\n\t\t\tmkdirSync(dirname(metaPath), { recursive: true });\n\t\t\twriteFileSync(metaPath, JSON.stringify(meta, null, 2), \"utf-8\");\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to persist conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate getConversationMetaPath(channelId: string): string | null {\n\t\tif (!this.config.stateDir) return null;\n\t\treturn join(this.config.stateDir, channelId, \".channel-meta.json\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../src/dingtalk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DingTalk communication layer using dingtalk-stream SDK with AI Card streaming.\n *\n * Handles:\n * - Receiving messages via DingTalk Stream Mode (DWClient)\n * - Responding via AI Card (streaming) or plain markdown (fallback)\n * - Access token management\n * - Per-channel message queuing\n */\nimport axios from \"axios\";\nimport { DWClient, type DWClientDownStream, type RobotMessage, TOPIC_ROBOT } from \"dingtalk-stream\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { parseBuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport * as log from \"./log.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DingTalkConfig {\n\tclientId: string;\n\tclientSecret: string;\n\trobotCode?: string;\n\tcardTemplateId?: string;\n\tcardTemplateKey?: string;\n\tallowFrom?: string[];\n\tstateDir?: string;\n}\n\nexport interface DingTalkEvent {\n\ttype: \"dm\" | \"group\";\n\tchannelId: string; // dm_{staffId} or group_{conversationId}\n\tts: string;\n\tuser: string; // sender staff id\n\tuserName: string; // sender nickname\n\ttext: string;\n\tconversationId: string;\n\tconversationType: string; // \"1\" = DM, \"2\" = group\n}\n\nexport interface DingTalkContext {\n\tmessage: {\n\t\ttext: string;\n\t\trawText: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\tchannel: string;\n\t\tts: string;\n\t};\n\tchannelName?: string;\n\trespond: (text: string, shouldLog?: boolean) => Promise<void>;\n\trespondPlain: (text: string, shouldLog?: boolean) => Promise<boolean>;\n\treplaceMessage: (text: string) => Promise<void>;\n\trespondInThread: (text: string) => Promise<void>;\n\tsetTyping: (isTyping: boolean) => Promise<void>;\n\tsetWorking: (working: boolean) => Promise<void>;\n\tdeleteMessage: () => Promise<void>;\n\tflush: () => Promise<void>;\n\tclose: () => Promise<void>;\n}\n\nexport type BusyMessageMode = \"steer\" | \"followUp\";\n\nexport interface DingTalkHandler {\n\tisRunning(channelId: string): boolean;\n\thandleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;\n\thandleStop(channelId: string, bot: DingTalkBot): Promise<void>;\n\thandleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<void>;\n}\n\n// ============================================================================\n// AI Card State\n// ============================================================================\n\ninterface AICard {\n\tinstanceId: string;\n\tconversationId: string;\n\taccessToken: string;\n\ttemplateKey: string;\n\tcreatedAt: number;\n\tlastUpdated: number;\n\tcontent: string;\n\tfinished: boolean;\n}\n\ninterface ConversationMeta {\n\tconversationId: string;\n\tconversationType: string;\n\tsenderId: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n\tprivate queue: QueuedWork[] = [];\n\tprivate processing = false;\n\n\tenqueue(work: QueuedWork): void {\n\t\tthis.queue.push(work);\n\t\tthis.processNext();\n\t}\n\n\tsize(): number {\n\t\treturn this.queue.length;\n\t}\n\n\tprivate async processNext(): Promise<void> {\n\t\tif (this.processing || this.queue.length === 0) return;\n\t\tthis.processing = true;\n\t\tconst work = this.queue.shift()!;\n\t\ttry {\n\t\t\tawait work();\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"Queue error\", err instanceof Error ? err.message : String(err));\n\t\t}\n\t\tthis.processing = false;\n\t\tthis.processNext();\n\t}\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DINGTALK_API = \"https://api.dingtalk.com\";\nconst TOKEN_REFRESH_SECS = 90 * 60; // 1.5 hours (tokens expire after 2 hours)\n\n// ============================================================================\n// DingTalkBot\n// ============================================================================\n\nexport class DingTalkBot {\n\tprivate handler: DingTalkHandler;\n\tprivate config: DingTalkConfig;\n\n\t// Access token cache\n\tprivate accessToken: string | null = null;\n\tprivate tokenExpiry = 0;\n\n\t// Active AI cards: channelId → AICard\n\tprivate activeCards = new Map<string, AICard>();\n\n\t// Conversation metadata cache: channelId → metadata\n\tprivate convMeta = new Map<string, ConversationMeta>();\n\n\t// Per-channel queues\n\tprivate queues = new Map<string, ChannelQueue>();\n\n\t// Connection stability\n\tprivate client: DWClient | null = null;\n\tprivate lastSocketAvailableTime = Date.now();\n\tprivate activeMessageProcessing = false;\n\tprivate keepAliveTimer: NodeJS.Timeout | null = null;\n\tprivate isReconnecting = false;\n\tprivate isStopped = false;\n\tprivate reconnectAttempts = 0;\n\n\t// Deduplication cache (Set for O(1) lookup, order array for FIFO eviction)\n\tprivate processedIds = new Set<string>();\n\tprivate processedIdsOrder: string[] = [];\n\n\tconstructor(handler: DingTalkHandler, config: DingTalkConfig) {\n\t\tthis.handler = handler;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Mark an ID as processed. Returns true if this is a new ID, false if already seen.\n\t * Maintains a FIFO buffer of at most 200 entries.\n\t */\n\tprivate markProcessed(id: string): boolean {\n\t\tif (this.processedIds.has(id)) return false;\n\t\tthis.processedIds.add(id);\n\t\tthis.processedIdsOrder.push(id);\n\t\twhile (this.processedIdsOrder.length > 200) {\n\t\t\tthis.processedIds.delete(this.processedIdsOrder.shift()!);\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// Public API\n\t// ==========================================================================\n\n\tasync start(): Promise<void> {\n\t\tif (!this.config.clientId || !this.config.clientSecret) {\n\t\t\tlog.logWarning(\"DingTalk: clientId / clientSecret not configured\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.config.cardTemplateId) {\n\t\t\tlog.logWarning(\"DingTalk: cardTemplateId not configured — AI Card streaming will not work\");\n\t\t}\n\n\t\tlog.logInfo(`DingTalk: initializing stream (clientId=${this.config.clientId.substring(0, 8)}…)`);\n\n\t\tif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\t\t\taxios.defaults.proxy = false;\n\t\t}\n\n\t\tthis.client = new DWClient({\n\t\t\tclientId: this.config.clientId,\n\t\t\tclientSecret: this.config.clientSecret,\n\t\t\tautoReconnect: false,\n\t\t\tkeepAlive: false,\n\t\t} as any);\n\n\t\tthis.client.registerCallbackListener(TOPIC_ROBOT, (msg: DWClientDownStream) => {\n\t\t\treturn this.handleRawMessage(msg);\n\t\t});\n\n\t\tlog.logConnected();\n\t\tawait this.doReconnect(true); // Initial connection\n\t}\n\n\tprivate handleRawMessage(msg: DWClientDownStream): { status: \"SUCCESS\"; message: string } {\n\t\t// 1. Immediate ACK\n\t\tif (msg.headers?.messageId && this.client) {\n\t\t\tthis.client.socketCallBackResponse(msg.headers.messageId, { status: \"SUCCESS\", message: \"OK\" });\n\t\t}\n\n\t\t// 2. Protocol deduplication\n\t\tconst messageId = msg.headers?.messageId;\n\t\tif (messageId && !this.markProcessed(messageId)) {\n\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t}\n\n\t\ttry {\n\t\t\tconst data: RobotMessage = typeof msg.data === \"string\" ? JSON.parse(msg.data) : msg.data;\n\n\t\t\t// 3. Business logic deduplication\n\t\t\tconst msgId = (data as any).msgId;\n\t\t\tif (msgId && !this.markProcessed(msgId)) {\n\t\t\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t\t\t}\n\n\t\t\t// Fire-and-forget processing\n\t\t\tthis.onStreamMessage(data).catch((err: unknown) => {\n\t\t\t\tlog.logWarning(\"DingTalk handler error\", err instanceof Error ? err.message : String(err));\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\"DingTalk: failed to parse message\", err instanceof Error ? err.message : String(err));\n\t\t}\n\n\t\treturn { status: \"SUCCESS\", message: \"OK\" };\n\t}\n\n\tprivate async doReconnect(immediate = false) {\n\t\tif (this.isReconnecting || this.isStopped || !this.client) return;\n\t\tthis.isReconnecting = true;\n\t\tlet connectionFailed = false;\n\n\t\tif (!immediate && this.reconnectAttempts > 0) {\n\t\t\tconst delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);\n\t\t\tlog.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t}\n\n\t\ttry {\n\t\t\tconst socket = (this.client as any).socket;\n\t\t\tif (socket?.readyState === 1 || socket?.readyState === 3) {\n\t\t\t\tawait (this.client as any).disconnect();\n\t\t\t}\n\n\t\t\tawait this.client.connect();\n\n\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\tthis.reconnectAttempts = 0; // Success, reset backoff\n\t\t\tlog.logInfo(\"DingTalk: connected to stream.\");\n\n\t\t\t// Setup keep alive\n\t\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\t\tthis.keepAliveTimer = setInterval(() => {\n\t\t\t\tif (this.isStopped) return;\n\n\t\t\t\tconst elapsed = Date.now() - this.lastSocketAvailableTime;\n\t\t\t\tif (elapsed > 90 * 1000 && !this.activeMessageProcessing) {\n\t\t\t\t\tlog.logWarning(\"DingTalk: connection timeout detected (>90s). Keeping active where possible...\");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst s = (this.client as any)?.socket;\n\t\t\t\t\tif (s?.readyState === 1) {\n\t\t\t\t\t\ts.ping();\n\t\t\t\t\t}\n\t\t\t\t} catch (_err) {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}, 30 * 1000);\n\n\t\t\t// Setup native socket events\n\t\t\tconst s = (this.client as any).socket;\n\n\t\t\ts?.on(\"pong\", () => {\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t});\n\n\t\t\ts?.on(\"close\", (code: number, reason: string) => {\n\t\t\t\tlog.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);\n\t\t\t\tif (this.isStopped) return;\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.doReconnect(true).catch((err) => {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: reconnect failed\", err instanceof Error ? err.message : String(err));\n\t\t\t\t\t});\n\t\t\t\t}, 1000);\n\t\t\t});\n\n\t\t\ts?.on(\"message\", (raw: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(raw);\n\t\t\t\t\tif (msg.type === \"SYSTEM\" && msg.headers?.topic === \"disconnect\") {\n\t\t\t\t\t\tlog.logWarning(\"DingTalk: disconnect event received from server.\");\n\t\t\t\t\t\tif (!this.isStopped) {\n\t\t\t\t\t\t\tthis.doReconnect(true).catch(() => {});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (_e) {\n\t\t\t\t\t// skip\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthis.reconnectAttempts++;\n\t\t\tconnectionFailed = true;\n\t\t\tlog.logWarning(\"DingTalk: connection failed\", err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tthis.isReconnecting = false;\n\t\t}\n\n\t\t// Auto-retry on failure with exponential backoff\n\t\tif (connectionFailed && !this.isStopped) {\n\t\t\tthis.doReconnect().catch(() => {});\n\t\t}\n\t}\n\n\tstop() {\n\t\tthis.isStopped = true;\n\t\tif (this.keepAliveTimer) clearInterval(this.keepAliveTimer);\n\t\tif (this.client) {\n\t\t\ttry {\n\t\t\t\t(this.client as any).disconnect();\n\t\t\t} catch (_e) {}\n\t\t}\n\t}\n\n\t/**\n\t * Enqueue an event for processing.\n\t * Returns true if enqueued, false if queue is full (max 5).\n\t */\n\tenqueueEvent(event: DingTalkEvent): boolean {\n\t\tconst queue = this.getQueue(event.channelId);\n\t\tif (queue.size() >= 5) {\n\t\t\tlog.logWarning(`Event queue full for ${event.channelId}, discarding: ${event.text.substring(0, 50)}`);\n\t\t\treturn false;\n\t\t}\n\t\tlog.logInfo(`Enqueueing event for ${event.channelId}: ${event.text.substring(0, 50)}`);\n\t\tqueue.enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this, true);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t// ==========================================================================\n\t// AI Card operations\n\t// ==========================================================================\n\n\t/**\n\t * Get or create an AI Card for a channel.\n\t */\n\tasync ensureCard(channelId: string): Promise<void> {\n\t\tif (!this.config.cardTemplateId) return;\n\t\tconst existing = this.activeCards.get(channelId);\n\t\tif (existing && !existing.finished) return;\n\t\tawait this.createCard(channelId);\n\t}\n\n\t/**\n\t * Stream content to the active AI Card for a channel.\n\t */\n\tasync streamToCard(channelId: string, content: string, finalize: boolean = false): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && !finalize && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\tif (finalize) {\n\t\t\t\treturn this.sendPlain(channelId, content);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tconst streamed = await this.streamCard(card, content, finalize);\n\t\tif (!streamed) {\n\t\t\tthis.activeCards.delete(channelId);\n\t\t}\n\t\treturn streamed;\n\t}\n\n\t/**\n\t * Finalize the active card for a channel without falling back to a plain message.\n\t * Returns true if a card was finalized, false if no active card existed.\n\t */\n\tasync finalizeExistingCard(channelId: string, content: string): Promise<boolean> {\n\t\tlet card = this.activeCards.get(channelId);\n\t\tif ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {\n\t\t\tawait this.ensureCard(channelId);\n\t\t\tcard = this.activeCards.get(channelId);\n\t\t}\n\t\tif (!card || card.finished) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst finalized = await this.streamCard(card, content, true);\n\t\tthis.activeCards.delete(channelId);\n\t\treturn finalized;\n\t}\n\n\t/**\n\t * Finalize and remove the active card for a channel.\n\t */\n\tasync finalizeCard(channelId: string, content: string): Promise<boolean> {\n\t\tconst finalized = await this.finalizeExistingCard(channelId, content);\n\t\tif (!finalized) {\n\t\t\treturn this.sendPlain(channelId, content);\n\t\t}\n\t\treturn true;\n\t}\n\n\tdiscardCard(channelId: string): void {\n\t\tthis.activeCards.delete(channelId);\n\t}\n\n\t/**\n\t * Send a normal message natively mapping DM and Group to correct endpoints (fallback when no card).\n\t */\n\tasync sendPlain(channelId: string, text: string): Promise<boolean> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return false;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot send plain message`);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\t\tconst isGroup = meta.conversationType === \"2\";\n\n\t\tconst hasMarkdown = /^#{1,6}\\s|^\\s*[-*]\\s|\\*\\*.*\\*\\*|```|`[^`]+`|\\[.*?\\]\\(.*?\\)/m.test(text);\n\n\t\tconst msgKey = hasMarkdown ? \"sampleMarkdown\" : \"sampleText\";\n\t\tconst msgParam = hasMarkdown ? JSON.stringify({ text, title: \"Bot\" }) : JSON.stringify({ content: text });\n\n\t\tconst url = isGroup\n\t\t\t? `${DINGTALK_API}/v1.0/robot/groupMessages/send`\n\t\t\t: `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;\n\n\t\tconst body: any = {\n\t\t\trobotCode,\n\t\t\tmsgKey,\n\t\t\tmsgParam,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.openConversationId = meta.conversationId;\n\t\t} else {\n\t\t\tbody.userIds = [meta.senderId];\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(url, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk plain send failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk plain send error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - AI Card implementation\n\t// ==========================================================================\n\n\tprivate async createCard(channelId: string): Promise<AICard | null> {\n\t\tconst token = await this.getAccessToken();\n\t\tif (!token) return null;\n\n\t\tconst meta = this.getConversationMeta(channelId);\n\t\tif (!meta) {\n\t\t\tlog.logWarning(`No conversation metadata for ${channelId}, cannot create card`);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isGroup = meta.conversationType === \"2\";\n\t\tconst instanceId = `card_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;\n\t\tconst robotCode = this.config.robotCode || this.config.clientId;\n\n\t\t// openSpaceId format:\n\t\t// 群聊: dtv1.card//IM_GROUP.{openConversationId}\n\t\t// 单聊: dtv1.card//IM_ROBOT.{userId}\n\t\tconst openSpaceId = isGroup\n\t\t\t? `dtv1.card//IM_GROUP.${meta.conversationId}`\n\t\t\t: `dtv1.card//IM_ROBOT.${meta.senderId}`;\n\n\t\tconst body: Record<string, unknown> = {\n\t\t\tcardTemplateId: this.config.cardTemplateId,\n\t\t\toutTrackId: instanceId,\n\t\t\tcardData: { cardParamMap: {} },\n\t\t\tcallbackType: \"STREAM\",\n\t\t\timGroupOpenSpaceModel: { supportForward: true },\n\t\t\timRobotOpenSpaceModel: { supportForward: true },\n\t\t\topenSpaceId,\n\t\t\tuserIdType: 1,\n\t\t};\n\n\t\tif (isGroup) {\n\t\t\tbody.imGroupOpenDeliverModel = { robotCode };\n\t\t} else {\n\t\t\tbody.imRobotOpenDeliverModel = { spaceType: \"IM_ROBOT\" };\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.post(`${DINGTALK_API}/v1.0/card/instances/createAndDeliver`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": token,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(`DingTalk Card: create failed (${err.response.status})`, JSON.stringify(err.response.data));\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: create failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tconst card: AICard = {\n\t\t\tinstanceId,\n\t\t\tconversationId: meta.conversationId,\n\t\t\taccessToken: token,\n\t\t\ttemplateKey: this.config.cardTemplateKey || \"content\",\n\t\t\tcreatedAt: Date.now() / 1000,\n\t\t\tlastUpdated: Date.now() / 1000,\n\t\t\tcontent: \"\",\n\t\t\tfinished: false,\n\t\t};\n\t\tthis.activeCards.set(channelId, card);\n\t\treturn card;\n\t}\n\n\tprivate async streamCard(card: AICard, content: string, finalize: boolean = false): Promise<boolean> {\n\t\t// Refresh token if needed\n\t\tconst ageSecs = Date.now() / 1000 - card.createdAt;\n\t\tif (ageSecs > TOKEN_REFRESH_SECS) {\n\t\t\tconst token = await this.getAccessToken();\n\t\t\tif (token) {\n\t\t\t\tcard.accessToken = token;\n\t\t\t}\n\t\t}\n\n\t\tconst body = {\n\t\t\toutTrackId: card.instanceId,\n\t\t\tguid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n\t\t\tkey: card.templateKey,\n\t\t\tcontent,\n\t\t\tisFull: true,\n\t\t\tisFinalize: finalize,\n\t\t\tisError: false,\n\t\t};\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tawait axios.put(`${DINGTALK_API}/v1.0/card/streaming`, body, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-acs-dingtalk-access-token\": card.accessToken,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst duration = Date.now() - start;\n\t\t\tif (duration > 1000) {\n\t\t\t\tlog.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);\n\t\t\t}\n\n\t\t\tcard.lastUpdated = Date.now() / 1000;\n\t\t\tcard.content = content;\n\t\t\tif (finalize) {\n\t\t\t\tcard.finished = true;\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk Card: streaming failed (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk Card: streaming failed\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Access Token\n\t// ==========================================================================\n\n\tprivate async getAccessToken(): Promise<string | null> {\n\t\tif (this.accessToken && Date.now() / 1000 < this.tokenExpiry) {\n\t\t\treturn this.accessToken;\n\t\t}\n\n\t\ttry {\n\t\t\tconst resp = await axios.post(\n\t\t\t\t`${DINGTALK_API}/v1.0/oauth2/accessToken`,\n\t\t\t\t{\n\t\t\t\t\tappKey: this.config.clientId,\n\t\t\t\t\tappSecret: this.config.clientSecret,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst data = resp.data as { accessToken?: string; expireIn?: number };\n\t\t\tthis.accessToken = data.accessToken || null;\n\t\t\tthis.tokenExpiry = Date.now() / 1000 + (data.expireIn || 7200) - 60;\n\t\t\treturn this.accessToken;\n\t\t} catch (err) {\n\t\t\tif (axios.isAxiosError(err) && err.response) {\n\t\t\t\tlog.logWarning(\n\t\t\t\t\t`DingTalk: failed to get access token (${err.response.status})`,\n\t\t\t\t\tJSON.stringify(err.response.data),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.logWarning(\"DingTalk: failed to get access token\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// ==========================================================================\n\t// Private - Message handling\n\t// ==========================================================================\n\n\tprivate extractContent(data: RobotMessage): string {\n\t\t// 1. text 类型消息:从 text.content 提取\n\t\tconst textContent = (data.text?.content || \"\").trim();\n\t\tif (textContent) return textContent;\n\n\t\t// 2. richText 类型消息:从 content.richText 列表提取文本片段\n\t\tconst raw = data as unknown as Record<string, unknown>;\n\t\tconst contentObj = raw.content as { richText?: Array<Record<string, string>> } | undefined;\n\t\tif (contentObj?.richText) {\n\t\t\tconst parts: string[] = [];\n\t\t\tfor (const item of contentObj.richText) {\n\t\t\t\tif (item.text) parts.push(item.text);\n\t\t\t}\n\t\t\tconst joined = parts.join(\"\").trim();\n\t\t\tif (joined) return joined;\n\t\t}\n\n\t\treturn \"\";\n\t}\n\n\tprivate async onStreamMessage(data: RobotMessage): Promise<void> {\n\t\tconst content = this.extractContent(data);\n\t\tconst senderId = data.senderStaffId || data.senderId || \"\";\n\t\tconst senderName = data.senderNick || \"Unknown\";\n\t\tconst conversationId = data.conversationId || \"\";\n\t\tconst conversationType = data.conversationType || \"1\";\n\n\t\tif (!content) {\n\t\t\tconst msgtype = (data as unknown as Record<string, unknown>).msgtype || \"unknown\";\n\t\t\tlog.logWarning(`DingTalk: empty message (type=${msgtype})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.allowFrom && this.config.allowFrom.length > 0) {\n\t\t\tif (!this.config.allowFrom.includes(senderId)) {\n\t\t\t\tlog.logWarning(`DingTalk: ignoring message from unauthorized user ${senderName} (${senderId})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Determine channel ID\n\t\tconst channelId = conversationType === \"2\" ? `group_${conversationId}` : `dm_${senderId}`;\n\n\t\tlog.logInfo(`DingTalk ← ${senderName} (${senderId}) [${channelId}]: ${content.substring(0, 80)}`);\n\n\t\t// Cache conversation metadata for card creation\n\t\tthis.setConversationMeta(channelId, {\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t\tsenderId,\n\t\t});\n\n\t\t// Build event\n\t\tconst event: DingTalkEvent = {\n\t\t\ttype: conversationType === \"2\" ? \"group\" : \"dm\",\n\t\t\tchannelId,\n\t\t\tts: Date.now().toString(),\n\t\t\tuser: senderId,\n\t\t\tuserName: senderName,\n\t\t\ttext: content,\n\t\t\tconversationId,\n\t\t\tconversationType,\n\t\t};\n\n\t\tconst builtInCommand = parseBuiltInCommand(content);\n\n\t\t// Check if busy\n\t\tif (this.handler.isRunning(channelId)) {\n\t\t\tif (builtInCommand?.name === \"help\") {\n\t\t\t\tawait this.sendPlain(channelId, renderBuiltInHelp());\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"stop\") {\n\t\t\t\tawait this.handler.handleStop(channelId, this);\n\t\t\t\tawait this.sendPlain(channelId, \"Stopping the current task.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"steer\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand?.name === \"followup\") {\n\t\t\t\tawait this.handler.handleBusyMessage(event, this, \"followUp\", builtInCommand.args);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tawait this.sendPlain(\n\t\t\t\t\tchannelId,\n\t\t\t\t\t\"A task is already running. Use `/stop`, `/steer <message>`, or `/followup <message>`. Plain messages default to steer.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this.handler.handleBusyMessage(event, this, \"steer\", content);\n\t\t\treturn;\n\t\t}\n\n\t\t// Enqueue for processing\n\t\tthis.getQueue(channelId).enqueue(async () => {\n\t\t\tthis.activeMessageProcessing = true;\n\t\t\ttry {\n\t\t\t\tawait this.handler.handleEvent(event, this);\n\t\t\t} finally {\n\t\t\t\tthis.activeMessageProcessing = false;\n\t\t\t\tthis.lastSocketAvailableTime = Date.now();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getQueue(channelId: string): ChannelQueue {\n\t\tlet queue = this.queues.get(channelId);\n\t\tif (!queue) {\n\t\t\tqueue = new ChannelQueue();\n\t\t\tthis.queues.set(channelId, queue);\n\t\t}\n\t\treturn queue;\n\t}\n\n\tprivate getConversationMeta(channelId: string): ConversationMeta | null {\n\t\tconst cached = this.convMeta.get(channelId);\n\t\tif (cached) return cached;\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath || !existsSync(metaPath)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf-8\")) as Partial<ConversationMeta>;\n\t\t\tif (!parsed.conversationId || !parsed.conversationType || !parsed.senderId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst meta: ConversationMeta = {\n\t\t\t\tconversationId: parsed.conversationId,\n\t\t\t\tconversationType: parsed.conversationType,\n\t\t\t\tsenderId: parsed.senderId,\n\t\t\t};\n\t\t\tthis.convMeta.set(channelId, meta);\n\t\t\treturn meta;\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to load conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate setConversationMeta(channelId: string, meta: ConversationMeta): void {\n\t\tthis.convMeta.set(channelId, meta);\n\n\t\tconst metaPath = this.getConversationMetaPath(channelId);\n\t\tif (!metaPath) return;\n\n\t\ttry {\n\t\t\tmkdirSync(dirname(metaPath), { recursive: true });\n\t\t\twriteFileSync(metaPath, JSON.stringify(meta, null, 2), \"utf-8\");\n\t\t} catch (err) {\n\t\t\tlog.logWarning(\n\t\t\t\t`Failed to persist conversation metadata for ${channelId}`,\n\t\t\t\terr instanceof Error ? err.message : String(err),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate getConversationMetaPath(channelId: string): string | null {\n\t\tif (!this.config.stateDir) return null;\n\t\treturn join(this.config.stateDir, channelId, \".channel-meta.json\");\n\t}\n}\n"]}
|