@wu529778790/open-im 1.11.1-beta.2 → 1.11.1-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -59
- package/README.zh-CN.md +75 -58
- package/dist/adapters/claude-sdk-adapter.d.ts +45 -19
- package/dist/adapters/claude-sdk-adapter.js +240 -414
- package/dist/adapters/registry.js +1 -2
- package/dist/adapters/tool-adapter.interface.d.ts +4 -0
- package/dist/clawbot/client.d.ts +1 -1
- package/dist/clawbot/client.js +71 -3
- package/dist/clawbot/event-handler.d.ts +1 -1
- package/dist/clawbot/event-handler.js +10 -3
- package/dist/commands/handler.d.ts +7 -0
- package/dist/commands/handler.js +270 -27
- package/dist/config-web-page-i18n.d.ts +96 -4
- package/dist/config-web-page-i18n.js +98 -4
- package/dist/config-web.js +15 -0
- package/dist/constants.js +0 -1
- package/dist/index.js +13 -4
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +21 -0
- package/dist/platform/handle-text-flow.d.ts +2 -0
- package/dist/platform/handle-text-flow.js +35 -2
- package/dist/session/session-manager.d.ts +9 -12
- package/dist/session/session-manager.js +31 -79
- package/dist/shared/ai-task.js +1 -0
- package/dist/shared/message-wal.d.ts +31 -0
- package/dist/shared/message-wal.js +91 -0
- package/package.json +4 -3
- package/web/dist/assets/index-B1c1xlbx.css +1 -0
- package/web/dist/assets/index-Dng7B2mE.js +57 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-7fl61tV1.css +0 -1
- package/web/dist/assets/index-BaLTMeeF.js +0 -57
|
@@ -161,8 +161,54 @@ export declare const PAGE_TEXTS: {
|
|
|
161
161
|
readonly tipQqSecret: "Same console → App Secret.";
|
|
162
162
|
readonly tipWeworkCorp: "WeCom admin → app → view Corp ID (or smart-bot Bot ID) and Secret.";
|
|
163
163
|
readonly tipDingtalkClient: "<a href=\"https://open-dev.dingtalk.com\" target=\"_blank\" rel=\"noopener\">DingTalk Open Platform</a> → internal app → App Key & App Secret.";
|
|
164
|
-
readonly tipWorkbuddyToken: "
|
|
165
|
-
readonly tipClawbotApiToken: "
|
|
164
|
+
readonly tipWorkbuddyToken: "Go to Settings → AI Tooling or use the Setup Wizard to configure WorkBuddy OAuth. You can also paste tokens from CodeBuddy login.";
|
|
165
|
+
readonly tipClawbotApiToken: "Use the \"Scan QR Login\" button in the platform card, or paste token from ClawBot iLink login.";
|
|
166
|
+
readonly navSetupWizard: "Setup Wizard";
|
|
167
|
+
readonly wizardStepLabel_welcome: "Welcome";
|
|
168
|
+
readonly wizardStepLabel_ai: "AI Backend";
|
|
169
|
+
readonly wizardStepLabel_platforms: "Platforms";
|
|
170
|
+
readonly wizardStepLabel_credentials: "Credentials";
|
|
171
|
+
readonly wizardStepLabel_review: "Review";
|
|
172
|
+
readonly wizardWelcomeTitle: "Welcome to open-im";
|
|
173
|
+
readonly wizardWelcomeDesc: "This wizard will guide you through configuring your AI coding assistant bridge. It takes about 2 minutes.";
|
|
174
|
+
readonly wizardStepAi: "Choose your AI backend and working directory";
|
|
175
|
+
readonly wizardStepPlatforms: "Pick which chat platforms to connect";
|
|
176
|
+
readonly wizardStepCredentials: "Enter API keys and tokens";
|
|
177
|
+
readonly wizardStepReview: "Review your choices, then save and start";
|
|
178
|
+
readonly wizardAiTitle: "AI Backend";
|
|
179
|
+
readonly wizardAiDesc: "open-im bridges chat apps to AI coding tools. Set up the paths below — the platform-level AI selection comes in the next step.";
|
|
180
|
+
readonly wizardWorkDirHint: "The directory where the AI tool will work. Leave empty to use the bridge's process directory.";
|
|
181
|
+
readonly wizardPlatformsTitle: "Choose Platforms";
|
|
182
|
+
readonly wizardPlatformsDesc: "Select at least one chat platform to connect. You can enable more later from the Dashboard.";
|
|
183
|
+
readonly wizardPickAtLeastOne: "Please select at least one platform.";
|
|
184
|
+
readonly wizardCredentialsTitle: "Enter Credentials";
|
|
185
|
+
readonly wizardCredentialsDesc: "Fill in the required credentials for each enabled platform.";
|
|
186
|
+
readonly wizardReviewTitle: "Review & Start";
|
|
187
|
+
readonly wizardReviewDesc: "Check your configuration below. When you're ready, click Save & Start to apply everything and launch the bridge.";
|
|
188
|
+
readonly wizardDoneTitle: "You're all set!";
|
|
189
|
+
readonly wizardDoneDesc: "The bridge is running. You can now message your bot on any enabled platform.";
|
|
190
|
+
readonly wizardGoToDashboard: "Go to Dashboard";
|
|
191
|
+
readonly wizardBack: "Back";
|
|
192
|
+
readonly wizardNext: "Next";
|
|
193
|
+
readonly wizardFinish: "Save & Start";
|
|
194
|
+
readonly wizardWorking: "Working...";
|
|
195
|
+
readonly wizardSaving: "Saving configuration...";
|
|
196
|
+
readonly wizardStarting: "Starting bridge...";
|
|
197
|
+
readonly wizardStepLabel_claudeApi: "Claude API";
|
|
198
|
+
readonly wizardStepClaudeApi: "Set up your Claude API access";
|
|
199
|
+
readonly wizardClaudeApiTitle: "Claude API Setup";
|
|
200
|
+
readonly wizardClaudeApiDesc: "Choose how to authenticate with Claude. This is required for the AI assistant to work.";
|
|
201
|
+
readonly wizardApiTypeLabel: "API Type";
|
|
202
|
+
readonly wizardApiOfficial: "Official Anthropic API";
|
|
203
|
+
readonly wizardApiOfficialDesc: "Use your Anthropic API key or auth token";
|
|
204
|
+
readonly wizardApiThirdparty: "Third-party Model";
|
|
205
|
+
readonly wizardApiThirdpartyDesc: "Use a compatible third-party API endpoint";
|
|
206
|
+
readonly wizardApiSkip: "Already configured";
|
|
207
|
+
readonly wizardApiSkipDesc: "Skip if you've already set up API access";
|
|
208
|
+
readonly wizardAuthTypeLabel: "Authentication";
|
|
209
|
+
readonly wizardAuthApiKey: "API Key";
|
|
210
|
+
readonly wizardAuthToken: "Auth Token";
|
|
211
|
+
readonly wizardLoading: "Loading...";
|
|
166
212
|
};
|
|
167
213
|
readonly zh: {
|
|
168
214
|
readonly pageTitle: "open-im 本地控制台";
|
|
@@ -323,7 +369,53 @@ export declare const PAGE_TEXTS: {
|
|
|
323
369
|
readonly tipQqSecret: "同一处获取 App Secret。";
|
|
324
370
|
readonly tipWeworkCorp: "企业微信管理后台 → 应用 → 查省 Corp ID / 智能机器人 Bot ID 与 Secret。";
|
|
325
371
|
readonly tipDingtalkClient: "<a href=\"https://open-dev.dingtalk.com\" target=\"_blank\" rel=\"noopener\">钉钉开放平台</a> → 企业内部应用 → AppKey / AppSecret。";
|
|
326
|
-
readonly tipWorkbuddyToken: "
|
|
327
|
-
readonly tipClawbotApiToken: "
|
|
372
|
+
readonly tipWorkbuddyToken: "前往“AI 工具配置”或使用设置向导配置 WorkBuddy OAuth;或粘贴 CodeBuddy 登录后的 Token。";
|
|
373
|
+
readonly tipClawbotApiToken: "使用平台卡片中的“扫码登录”按钮,或从 ClawBot iLink 登录后复制 Token。";
|
|
374
|
+
readonly navSetupWizard: "设置向导";
|
|
375
|
+
readonly wizardStepLabel_welcome: "欢迎";
|
|
376
|
+
readonly wizardStepLabel_ai: "AI 后端";
|
|
377
|
+
readonly wizardStepLabel_platforms: "平台";
|
|
378
|
+
readonly wizardStepLabel_credentials: "凭证";
|
|
379
|
+
readonly wizardStepLabel_review: "确认";
|
|
380
|
+
readonly wizardWelcomeTitle: "欢迎使用 open-im";
|
|
381
|
+
readonly wizardWelcomeDesc: "这个向导将引导你配置 AI 编程助手桥接。大约需要 2 分钟。";
|
|
382
|
+
readonly wizardStepAi: "选择 AI 后端和工作目录";
|
|
383
|
+
readonly wizardStepPlatforms: "选择要连接的聊天平台";
|
|
384
|
+
readonly wizardStepCredentials: "输入 API Key 和 Token";
|
|
385
|
+
readonly wizardStepReview: "检查配置,然后保存启动";
|
|
386
|
+
readonly wizardAiTitle: "AI 后端";
|
|
387
|
+
readonly wizardAiDesc: "open-im 将聊天 App 桥接到 AI 编程工具。在下方设置路径,平台级的 AI 选择在下一步。";
|
|
388
|
+
readonly wizardWorkDirHint: "AI 工具的工作目录。留空则使用桥接进程的工作目录。";
|
|
389
|
+
readonly wizardPlatformsTitle: "选择平台";
|
|
390
|
+
readonly wizardPlatformsDesc: "至少选择一个聊天平台。后续可在概览页启用更多。";
|
|
391
|
+
readonly wizardPickAtLeastOne: "请至少选择一个平台。";
|
|
392
|
+
readonly wizardCredentialsTitle: "输入凭证";
|
|
393
|
+
readonly wizardCredentialsDesc: "为每个已启用的平台填写必要的凭证信息。";
|
|
394
|
+
readonly wizardReviewTitle: "确认并启动";
|
|
395
|
+
readonly wizardReviewDesc: "检查下方配置。准备好后点击“保存并启动”应用配置并启动桥接。";
|
|
396
|
+
readonly wizardDoneTitle: "配置完成!";
|
|
397
|
+
readonly wizardDoneDesc: "桥接已运行。你现在可以在任何已启用的平台上向机器人发消息了。";
|
|
398
|
+
readonly wizardGoToDashboard: "进入概览";
|
|
399
|
+
readonly wizardBack: "上一步";
|
|
400
|
+
readonly wizardNext: "下一步";
|
|
401
|
+
readonly wizardFinish: "保存并启动";
|
|
402
|
+
readonly wizardWorking: "处理中...";
|
|
403
|
+
readonly wizardSaving: "正在保存配置...";
|
|
404
|
+
readonly wizardStarting: "正在启动桥接...";
|
|
405
|
+
readonly wizardStepLabel_claudeApi: "Claude API";
|
|
406
|
+
readonly wizardStepClaudeApi: "配置 Claude API 访问";
|
|
407
|
+
readonly wizardClaudeApiTitle: "Claude API 配置";
|
|
408
|
+
readonly wizardClaudeApiDesc: "选择 Claude 的认证方式,这是 AI 助手工作的必要配置。";
|
|
409
|
+
readonly wizardApiTypeLabel: "API 类型";
|
|
410
|
+
readonly wizardApiOfficial: "官方 Anthropic API";
|
|
411
|
+
readonly wizardApiOfficialDesc: "使用 Anthropic API Key 或 Auth Token";
|
|
412
|
+
readonly wizardApiThirdparty: "第三方模型";
|
|
413
|
+
readonly wizardApiThirdpartyDesc: "使用兼容的第三方 API 端点";
|
|
414
|
+
readonly wizardApiSkip: "已配置";
|
|
415
|
+
readonly wizardApiSkipDesc: "如果已配置 API 访问,可跳过此步";
|
|
416
|
+
readonly wizardAuthTypeLabel: "认证方式";
|
|
417
|
+
readonly wizardAuthApiKey: "API Key";
|
|
418
|
+
readonly wizardAuthToken: "Auth Token";
|
|
419
|
+
readonly wizardLoading: "加载中...";
|
|
328
420
|
};
|
|
329
421
|
};
|
|
@@ -161,8 +161,55 @@ export const PAGE_TEXTS = {
|
|
|
161
161
|
tipQqSecret: "Same console → App Secret.",
|
|
162
162
|
tipWeworkCorp: "WeCom admin → app → view Corp ID (or smart-bot Bot ID) and Secret.",
|
|
163
163
|
tipDingtalkClient: '<a href="https://open-dev.dingtalk.com" target="_blank" rel="noopener">DingTalk Open Platform</a> → internal app → App Key & App Secret.',
|
|
164
|
-
tipWorkbuddyToken: '
|
|
165
|
-
tipClawbotApiToken: '
|
|
164
|
+
tipWorkbuddyToken: 'Go to Settings → AI Tooling or use the Setup Wizard to configure WorkBuddy OAuth. You can also paste tokens from CodeBuddy login.',
|
|
165
|
+
tipClawbotApiToken: 'Use the "Scan QR Login" button in the platform card, or paste token from ClawBot iLink login.',
|
|
166
|
+
/* ---- Setup Wizard ---- */
|
|
167
|
+
navSetupWizard: "Setup Wizard",
|
|
168
|
+
wizardStepLabel_welcome: "Welcome",
|
|
169
|
+
wizardStepLabel_ai: "AI Backend",
|
|
170
|
+
wizardStepLabel_platforms: "Platforms",
|
|
171
|
+
wizardStepLabel_credentials: "Credentials",
|
|
172
|
+
wizardStepLabel_review: "Review",
|
|
173
|
+
wizardWelcomeTitle: "Welcome to open-im",
|
|
174
|
+
wizardWelcomeDesc: "This wizard will guide you through configuring your AI coding assistant bridge. It takes about 2 minutes.",
|
|
175
|
+
wizardStepAi: "Choose your AI backend and working directory",
|
|
176
|
+
wizardStepPlatforms: "Pick which chat platforms to connect",
|
|
177
|
+
wizardStepCredentials: "Enter API keys and tokens",
|
|
178
|
+
wizardStepReview: "Review your choices, then save and start",
|
|
179
|
+
wizardAiTitle: "AI Backend",
|
|
180
|
+
wizardAiDesc: "open-im bridges chat apps to AI coding tools. Set up the paths below — the platform-level AI selection comes in the next step.",
|
|
181
|
+
wizardWorkDirHint: "The directory where the AI tool will work. Leave empty to use the bridge's process directory.",
|
|
182
|
+
wizardPlatformsTitle: "Choose Platforms",
|
|
183
|
+
wizardPlatformsDesc: "Select at least one chat platform to connect. You can enable more later from the Dashboard.",
|
|
184
|
+
wizardPickAtLeastOne: "Please select at least one platform.",
|
|
185
|
+
wizardCredentialsTitle: "Enter Credentials",
|
|
186
|
+
wizardCredentialsDesc: "Fill in the required credentials for each enabled platform.",
|
|
187
|
+
wizardReviewTitle: "Review & Start",
|
|
188
|
+
wizardReviewDesc: "Check your configuration below. When you're ready, click Save & Start to apply everything and launch the bridge.",
|
|
189
|
+
wizardDoneTitle: "You're all set!",
|
|
190
|
+
wizardDoneDesc: "The bridge is running. You can now message your bot on any enabled platform.",
|
|
191
|
+
wizardGoToDashboard: "Go to Dashboard",
|
|
192
|
+
wizardBack: "Back",
|
|
193
|
+
wizardNext: "Next",
|
|
194
|
+
wizardFinish: "Save & Start",
|
|
195
|
+
wizardWorking: "Working...",
|
|
196
|
+
wizardSaving: "Saving configuration...",
|
|
197
|
+
wizardStarting: "Starting bridge...",
|
|
198
|
+
wizardStepLabel_claudeApi: "Claude API",
|
|
199
|
+
wizardStepClaudeApi: "Set up your Claude API access",
|
|
200
|
+
wizardClaudeApiTitle: "Claude API Setup",
|
|
201
|
+
wizardClaudeApiDesc: "Choose how to authenticate with Claude. This is required for the AI assistant to work.",
|
|
202
|
+
wizardApiTypeLabel: "API Type",
|
|
203
|
+
wizardApiOfficial: "Official Anthropic API",
|
|
204
|
+
wizardApiOfficialDesc: "Use your Anthropic API key or auth token",
|
|
205
|
+
wizardApiThirdparty: "Third-party Model",
|
|
206
|
+
wizardApiThirdpartyDesc: "Use a compatible third-party API endpoint",
|
|
207
|
+
wizardApiSkip: "Already configured",
|
|
208
|
+
wizardApiSkipDesc: "Skip if you've already set up API access",
|
|
209
|
+
wizardAuthTypeLabel: "Authentication",
|
|
210
|
+
wizardAuthApiKey: "API Key",
|
|
211
|
+
wizardAuthToken: "Auth Token",
|
|
212
|
+
wizardLoading: "Loading...",
|
|
166
213
|
},
|
|
167
214
|
zh: {
|
|
168
215
|
pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
|
|
@@ -323,7 +370,54 @@ export const PAGE_TEXTS = {
|
|
|
323
370
|
tipQqSecret: "\u540c\u4e00\u5904\u83b7\u53d6 App Secret\u3002",
|
|
324
371
|
tipWeworkCorp: "\u4f01\u4e1a\u5fae\u4fe1\u7ba1\u7406\u540e\u53f0 \u2192 \u5e94\u7528 \u2192 \u67e5\u7701 Corp ID / \u667a\u80fd\u673a\u5668\u4eba Bot ID \u4e0e Secret\u3002",
|
|
325
372
|
tipDingtalkClient: '<a href="https://open-dev.dingtalk.com" target="_blank" rel="noopener">\u9489\u9489\u5f00\u653e\u5e73\u53f0</a> \u2192 \u4f01\u4e1a\u5185\u90e8\u5e94\u7528 \u2192 AppKey / AppSecret\u3002',
|
|
326
|
-
tipWorkbuddyToken: "\
|
|
327
|
-
tipClawbotApiToken: '\
|
|
373
|
+
tipWorkbuddyToken: "\u524d\u5f80\u201cAI \u5de5\u5177\u914d\u7f6e\u201d\u6216\u4f7f\u7528\u8bbe\u7f6e\u5411\u5bfc\u914d\u7f6e WorkBuddy OAuth\uff1b\u6216\u7c98\u8d34 CodeBuddy \u767b\u5f55\u540e\u7684 Token\u3002",
|
|
374
|
+
tipClawbotApiToken: '\u4f7f\u7528\u5e73\u53f0\u5361\u7247\u4e2d\u7684\u201c\u626b\u7801\u767b\u5f55\u201d\u6309\u94ae\uff0c\u6216\u4ece ClawBot iLink \u767b\u5f55\u540e\u590d\u5236 Token\u3002',
|
|
375
|
+
/* ---- Setup Wizard ---- */
|
|
376
|
+
navSetupWizard: "\u8bbe\u7f6e\u5411\u5bfc",
|
|
377
|
+
wizardStepLabel_welcome: "\u6b22\u8fce",
|
|
378
|
+
wizardStepLabel_ai: "AI \u540e\u7aef",
|
|
379
|
+
wizardStepLabel_platforms: "\u5e73\u53f0",
|
|
380
|
+
wizardStepLabel_credentials: "\u51ed\u8bc1",
|
|
381
|
+
wizardStepLabel_review: "\u786e\u8ba4",
|
|
382
|
+
wizardWelcomeTitle: "\u6b22\u8fce\u4f7f\u7528 open-im",
|
|
383
|
+
wizardWelcomeDesc: "\u8fd9\u4e2a\u5411\u5bfc\u5c06\u5f15\u5bfc\u4f60\u914d\u7f6e AI \u7f16\u7a0b\u52a9\u624b\u6865\u63a5\u3002\u5927\u7ea6\u9700\u8981 2 \u5206\u949f\u3002",
|
|
384
|
+
wizardStepAi: "\u9009\u62e9 AI \u540e\u7aef\u548c\u5de5\u4f5c\u76ee\u5f55",
|
|
385
|
+
wizardStepPlatforms: "\u9009\u62e9\u8981\u8fde\u63a5\u7684\u804a\u5929\u5e73\u53f0",
|
|
386
|
+
wizardStepCredentials: "\u8f93\u5165 API Key \u548c Token",
|
|
387
|
+
wizardStepReview: "\u68c0\u67e5\u914d\u7f6e\uff0c\u7136\u540e\u4fdd\u5b58\u542f\u52a8",
|
|
388
|
+
wizardAiTitle: "AI \u540e\u7aef",
|
|
389
|
+
wizardAiDesc: "open-im \u5c06\u804a\u5929 App \u6865\u63a5\u5230 AI \u7f16\u7a0b\u5de5\u5177\u3002\u5728\u4e0b\u65b9\u8bbe\u7f6e\u8def\u5f84\uff0c\u5e73\u53f0\u7ea7\u7684 AI \u9009\u62e9\u5728\u4e0b\u4e00\u6b65\u3002",
|
|
390
|
+
wizardWorkDirHint: "AI \u5de5\u5177\u7684\u5de5\u4f5c\u76ee\u5f55\u3002\u7559\u7a7a\u5219\u4f7f\u7528\u6865\u63a5\u8fdb\u7a0b\u7684\u5de5\u4f5c\u76ee\u5f55\u3002",
|
|
391
|
+
wizardPlatformsTitle: "\u9009\u62e9\u5e73\u53f0",
|
|
392
|
+
wizardPlatformsDesc: "\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u804a\u5929\u5e73\u53f0\u3002\u540e\u7eed\u53ef\u5728\u6982\u89c8\u9875\u542f\u7528\u66f4\u591a\u3002",
|
|
393
|
+
wizardPickAtLeastOne: "\u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u5e73\u53f0\u3002",
|
|
394
|
+
wizardCredentialsTitle: "\u8f93\u5165\u51ed\u8bc1",
|
|
395
|
+
wizardCredentialsDesc: "\u4e3a\u6bcf\u4e2a\u5df2\u542f\u7528\u7684\u5e73\u53f0\u586b\u5199\u5fc5\u8981\u7684\u51ed\u8bc1\u4fe1\u606f\u3002",
|
|
396
|
+
wizardReviewTitle: "\u786e\u8ba4\u5e76\u542f\u52a8",
|
|
397
|
+
wizardReviewDesc: "\u68c0\u67e5\u4e0b\u65b9\u914d\u7f6e\u3002\u51c6\u5907\u597d\u540e\u70b9\u51fb\u201c\u4fdd\u5b58\u5e76\u542f\u52a8\u201d\u5e94\u7528\u914d\u7f6e\u5e76\u542f\u52a8\u6865\u63a5\u3002",
|
|
398
|
+
wizardDoneTitle: "\u914d\u7f6e\u5b8c\u6210\uff01",
|
|
399
|
+
wizardDoneDesc: "\u6865\u63a5\u5df2\u8fd0\u884c\u3002\u4f60\u73b0\u5728\u53ef\u4ee5\u5728\u4efb\u4f55\u5df2\u542f\u7528\u7684\u5e73\u53f0\u4e0a\u5411\u673a\u5668\u4eba\u53d1\u6d88\u606f\u4e86\u3002",
|
|
400
|
+
wizardGoToDashboard: "\u8fdb\u5165\u6982\u89c8",
|
|
401
|
+
wizardBack: "\u4e0a\u4e00\u6b65",
|
|
402
|
+
wizardNext: "\u4e0b\u4e00\u6b65",
|
|
403
|
+
wizardFinish: "\u4fdd\u5b58\u5e76\u542f\u52a8",
|
|
404
|
+
wizardWorking: "\u5904\u7406\u4e2d...",
|
|
405
|
+
wizardSaving: "\u6b63\u5728\u4fdd\u5b58\u914d\u7f6e...",
|
|
406
|
+
wizardStarting: "\u6b63\u5728\u542f\u52a8\u6865\u63a5...",
|
|
407
|
+
wizardStepLabel_claudeApi: "Claude API",
|
|
408
|
+
wizardStepClaudeApi: "\u914d\u7f6e Claude API \u8bbf\u95ee",
|
|
409
|
+
wizardClaudeApiTitle: "Claude API \u914d\u7f6e",
|
|
410
|
+
wizardClaudeApiDesc: "\u9009\u62e9 Claude \u7684\u8ba4\u8bc1\u65b9\u5f0f\uff0c\u8fd9\u662f AI \u52a9\u624b\u5de5\u4f5c\u7684\u5fc5\u8981\u914d\u7f6e\u3002",
|
|
411
|
+
wizardApiTypeLabel: "API \u7c7b\u578b",
|
|
412
|
+
wizardApiOfficial: "\u5b98\u65b9 Anthropic API",
|
|
413
|
+
wizardApiOfficialDesc: "\u4f7f\u7528 Anthropic API Key \u6216 Auth Token",
|
|
414
|
+
wizardApiThirdparty: "\u7b2c\u4e09\u65b9\u6a21\u578b",
|
|
415
|
+
wizardApiThirdpartyDesc: "\u4f7f\u7528\u517c\u5bb9\u7684\u7b2c\u4e09\u65b9 API \u7aef\u70b9",
|
|
416
|
+
wizardApiSkip: "\u5df2\u914d\u7f6e",
|
|
417
|
+
wizardApiSkipDesc: "\u5982\u679c\u5df2\u914d\u7f6e API \u8bbf\u95ee\uff0c\u53ef\u8df3\u8fc7\u6b64\u6b65",
|
|
418
|
+
wizardAuthTypeLabel: "\u8ba4\u8bc1\u65b9\u5f0f",
|
|
419
|
+
wizardAuthApiKey: "API Key",
|
|
420
|
+
wizardAuthToken: "Auth Token",
|
|
421
|
+
wizardLoading: "\u52a0\u8f7d\u4e2d...",
|
|
328
422
|
}
|
|
329
423
|
};
|
package/dist/config-web.js
CHANGED
|
@@ -1095,6 +1095,21 @@ export async function startWebConfigServer(options) {
|
|
|
1095
1095
|
json(response, 200, { platforms, serviceStatus: getServiceStatus() }, request);
|
|
1096
1096
|
return;
|
|
1097
1097
|
}
|
|
1098
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/metrics") {
|
|
1099
|
+
const file = loadFileConfig();
|
|
1100
|
+
const platforms = getHealthPlatformSnapshot(file);
|
|
1101
|
+
const status = getServiceStatus();
|
|
1102
|
+
const uptime = process.uptime();
|
|
1103
|
+
const mem = process.memoryUsage();
|
|
1104
|
+
json(response, 200, {
|
|
1105
|
+
uptime: Math.round(uptime),
|
|
1106
|
+
memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal },
|
|
1107
|
+
service: { running: status.running, pid: status.pid },
|
|
1108
|
+
platforms: Object.entries(platforms).map(([k, v]) => ({ name: k, configured: v.configured, enabled: v.enabled })),
|
|
1109
|
+
timestamp: new Date().toISOString(),
|
|
1110
|
+
}, request);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1098
1113
|
if (request.method === "POST" && requestUrl.pathname === "/api/service/start") {
|
|
1099
1114
|
try {
|
|
1100
1115
|
const config = loadConfig();
|
package/dist/constants.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -154,7 +154,7 @@ function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, se
|
|
|
154
154
|
`📱 平台: ${platformName}`,
|
|
155
155
|
`🤖 AI: ${toolName}`,
|
|
156
156
|
`📁 目录: ${dir}`,
|
|
157
|
-
].join("\n");
|
|
157
|
+
].join("\n\n");
|
|
158
158
|
}
|
|
159
159
|
function buildShutdownMessage(uptimeMinutes) {
|
|
160
160
|
const uptime = uptimeMinutes < 1 ? '< 1' : String(uptimeMinutes);
|
|
@@ -162,7 +162,7 @@ function buildShutdownMessage(uptimeMinutes) {
|
|
|
162
162
|
`🛑 open-im 正在关闭`,
|
|
163
163
|
"",
|
|
164
164
|
`⏱️ 运行时长: ${uptime} 分钟`,
|
|
165
|
-
].join("\n");
|
|
165
|
+
].join("\n\n");
|
|
166
166
|
}
|
|
167
167
|
export async function main() {
|
|
168
168
|
const startupCwd = process.cwd();
|
|
@@ -330,8 +330,17 @@ export async function main() {
|
|
|
330
330
|
await closeLogger();
|
|
331
331
|
process.exit(0);
|
|
332
332
|
};
|
|
333
|
-
process.on("SIGINT", () =>
|
|
334
|
-
|
|
333
|
+
process.on("SIGINT", () => {
|
|
334
|
+
// 优雅关闭超时:10 秒后强制退出
|
|
335
|
+
const forceExit = setTimeout(() => { log.warn('Shutdown timeout, forcing exit'); process.exit(1); }, 10_000);
|
|
336
|
+
forceExit.unref();
|
|
337
|
+
shutdown().catch(() => process.exit(1));
|
|
338
|
+
});
|
|
339
|
+
process.on("SIGTERM", () => {
|
|
340
|
+
const forceExit = setTimeout(() => { log.warn('Shutdown timeout, forcing exit'); process.exit(1); }, 10_000);
|
|
341
|
+
forceExit.unref();
|
|
342
|
+
shutdown().catch(() => process.exit(1));
|
|
343
|
+
});
|
|
335
344
|
// 兜底:进程退出(含异常路径,如未捕获异常 / SIGKILL)时强制清理 CLI 子进程,
|
|
336
345
|
// 避免僵尸 / 孤儿。正常 shutdown 已在 cleanupAdapters() 里清理过。
|
|
337
346
|
process.on("exit", () => {
|
package/dist/logger.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export declare function createLogger(tag: string): {
|
|
|
23
23
|
debug: (msg: string, ...args: unknown[]) => void;
|
|
24
24
|
infoEvent: (event: string, data?: Record<string, unknown>, msg?: string) => void;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Audit log — records user interactions for debugging and compliance.
|
|
28
|
+
* Always enabled, writes to audit.log.
|
|
29
|
+
*/
|
|
30
|
+
export declare function auditLog(platform: string, userId: string, action: string, detail?: Record<string, unknown>): void;
|
|
26
31
|
export declare function emitStructuredEvent(tag: string, event: string, data?: Record<string, unknown>, level?: LogLevel, msg?: string): void;
|
|
27
32
|
export declare function shutdownLoggerTelemetry(): Promise<void>;
|
|
28
33
|
export declare function closeLogger(): Promise<void>;
|
package/dist/logger.js
CHANGED
|
@@ -12,6 +12,7 @@ let logDir = DEFAULT_LOG_DIR;
|
|
|
12
12
|
let minLevel = LOG_LEVELS.DEBUG;
|
|
13
13
|
let logStream;
|
|
14
14
|
let eventsStream;
|
|
15
|
+
let auditStream;
|
|
15
16
|
let telemetryEnabled = false;
|
|
16
17
|
let telemetryStatsTimer = null;
|
|
17
18
|
let lastTelemetryStatsSignature = '';
|
|
@@ -77,6 +78,12 @@ export function initLogger(dirOrOpts, level, telemetry) {
|
|
|
77
78
|
mkdirSync(logDir, { recursive: true });
|
|
78
79
|
rotateOldLogs();
|
|
79
80
|
logStream = createWriteStream(join(logDir, getLogFileName()), { flags: 'a' });
|
|
81
|
+
// Audit log — always enabled, separate file
|
|
82
|
+
if (auditStream) {
|
|
83
|
+
auditStream.end();
|
|
84
|
+
auditStream = undefined;
|
|
85
|
+
}
|
|
86
|
+
auditStream = createWriteStream(join(logDir, 'audit.log'), { flags: 'a' });
|
|
80
87
|
telemetryEnabled = !!tel?.enabled;
|
|
81
88
|
if (telemetryStatsTimer) {
|
|
82
89
|
clearInterval(telemetryStatsTimer);
|
|
@@ -135,6 +142,20 @@ export function createLogger(tag) {
|
|
|
135
142
|
infoEvent: (event, data, msg) => emitStructuredEvent(tag, event, data, 'INFO', msg),
|
|
136
143
|
};
|
|
137
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Audit log — records user interactions for debugging and compliance.
|
|
147
|
+
* Always enabled, writes to audit.log.
|
|
148
|
+
*/
|
|
149
|
+
export function auditLog(platform, userId, action, detail) {
|
|
150
|
+
const entry = {
|
|
151
|
+
ts: new Date().toISOString(),
|
|
152
|
+
platform,
|
|
153
|
+
userId,
|
|
154
|
+
action,
|
|
155
|
+
...detail,
|
|
156
|
+
};
|
|
157
|
+
auditStream?.write(JSON.stringify(entry) + '\n');
|
|
158
|
+
}
|
|
138
159
|
export function emitStructuredEvent(tag, event, data, level = 'INFO', msg = '') {
|
|
139
160
|
if (!telemetryEnabled)
|
|
140
161
|
return;
|
|
@@ -38,6 +38,8 @@ export interface HandleTextFlowParams {
|
|
|
38
38
|
chatId: string;
|
|
39
39
|
/** The trimmed text content of the message. */
|
|
40
40
|
text: string;
|
|
41
|
+
/** Optional message ID for deduplication. */
|
|
42
|
+
msgId?: string;
|
|
41
43
|
/** The platform event context (accessControl, commandHandler, requestQueue, etc.). */
|
|
42
44
|
ctx: PlatformEventContext;
|
|
43
45
|
/** The platform-specific AI request handler (from createPlatformAIRequestHandler). */
|
|
@@ -17,8 +17,29 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { setActiveChatId } from '../shared/active-chats.js';
|
|
19
19
|
import { setChatUser } from '../shared/chat-user-map.js';
|
|
20
|
-
import { createLogger } from '../logger.js';
|
|
20
|
+
import { createLogger, auditLog } from '../logger.js';
|
|
21
21
|
import { handleEnqueueResult, DEFAULT_QUEUE_FULL_MESSAGE, DEFAULT_QUEUED_MESSAGE } from '../shared/utils.js';
|
|
22
|
+
import { walWrite, walCommit } from '../shared/message-wal.js';
|
|
23
|
+
/* ── 幂等性:消息去重 ── */
|
|
24
|
+
const DEDUP_TTL_MS = 60_000; // 1 分钟内的相同 msgId 视为重复
|
|
25
|
+
const dedupCache = new Map(); // msgId → timestamp
|
|
26
|
+
function isDuplicate(msgId) {
|
|
27
|
+
if (!msgId)
|
|
28
|
+
return false;
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const prev = dedupCache.get(msgId);
|
|
31
|
+
if (prev && now - prev < DEDUP_TTL_MS)
|
|
32
|
+
return true;
|
|
33
|
+
dedupCache.set(msgId, now);
|
|
34
|
+
// 清理过期条目
|
|
35
|
+
if (dedupCache.size > 1000) {
|
|
36
|
+
for (const [k, v] of dedupCache) {
|
|
37
|
+
if (now - v > DEDUP_TTL_MS)
|
|
38
|
+
dedupCache.delete(k);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
22
43
|
const log = createLogger('TextFlow');
|
|
23
44
|
/** Default access-denied message. */
|
|
24
45
|
function defaultAccessDeniedMessage(userId) {
|
|
@@ -38,13 +59,21 @@ function defaultAccessDeniedMessage(userId) {
|
|
|
38
59
|
* @returns true if the message was processed (handled or enqueued), false if denied.
|
|
39
60
|
*/
|
|
40
61
|
export async function handleTextFlow(params) {
|
|
41
|
-
const { platform, userId, chatId, text, ctx, handleAIRequest, sendTextReply, workDir: workDirOverride, convId: convIdOverride, replyToMessageId, accessDeniedMessage = defaultAccessDeniedMessage, queueFullMessage = DEFAULT_QUEUE_FULL_MESSAGE, queuedMessage = DEFAULT_QUEUED_MESSAGE, customEnqueue, } = params;
|
|
62
|
+
const { platform, userId, chatId, text, msgId, ctx, handleAIRequest, sendTextReply, workDir: workDirOverride, convId: convIdOverride, replyToMessageId, accessDeniedMessage = defaultAccessDeniedMessage, queueFullMessage = DEFAULT_QUEUE_FULL_MESSAGE, queuedMessage = DEFAULT_QUEUED_MESSAGE, customEnqueue, } = params;
|
|
42
63
|
// 1. Access control check
|
|
43
64
|
if (!ctx.accessControl.isAllowed(userId)) {
|
|
44
65
|
log.info(`[${platform}] Access denied for user: ${userId}`);
|
|
45
66
|
await sendTextReply(chatId, accessDeniedMessage(userId));
|
|
46
67
|
return false;
|
|
47
68
|
}
|
|
69
|
+
// 1b. Deduplication check
|
|
70
|
+
if (isDuplicate(msgId)) {
|
|
71
|
+
log.info(`[${platform}] Duplicate message ignored: msgId=${msgId}`);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// 1c. WAL: persist message before processing
|
|
75
|
+
const walMsgId = msgId || `${platform}:${userId}:${Date.now()}`;
|
|
76
|
+
walWrite({ msgId: walMsgId, platform, userId, chatId, text: text.substring(0, 500), timestamp: Date.now() });
|
|
48
77
|
// 2. Set active chat ID
|
|
49
78
|
setActiveChatId(platform, chatId);
|
|
50
79
|
// 3. Set chat-user mapping
|
|
@@ -69,6 +98,7 @@ export async function handleTextFlow(params) {
|
|
|
69
98
|
};
|
|
70
99
|
const handled = await ctx.commandHandler.dispatch(text, chatId, userId, platform, dispatchHandler, commandSender);
|
|
71
100
|
if (handled) {
|
|
101
|
+
walCommit(walMsgId);
|
|
72
102
|
return true;
|
|
73
103
|
}
|
|
74
104
|
}
|
|
@@ -79,6 +109,8 @@ export async function handleTextFlow(params) {
|
|
|
79
109
|
if (!text) {
|
|
80
110
|
return true;
|
|
81
111
|
}
|
|
112
|
+
// Audit: log user interaction
|
|
113
|
+
auditLog(platform, userId, 'message', { text: text.substring(0, 200) });
|
|
82
114
|
// 6. Enqueue AI request
|
|
83
115
|
if (customEnqueue) {
|
|
84
116
|
// Platform-specific enqueue (e.g., WorkBuddy with extra context)
|
|
@@ -103,5 +135,6 @@ export async function handleTextFlow(params) {
|
|
|
103
135
|
});
|
|
104
136
|
await handleEnqueueResult(enqueueResult, (text) => sendTextReply(chatId, text), { queueFull: queueFullMessage, queued: queuedMessage });
|
|
105
137
|
}
|
|
138
|
+
walCommit(walMsgId);
|
|
106
139
|
return true;
|
|
107
140
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
type ToolId = 'claude' | 'codex' | 'codebuddy' | 'opencode';
|
|
2
|
-
interface ConvHistoryEntry {
|
|
3
|
-
convId: string;
|
|
4
|
-
totalTurns: number;
|
|
5
|
-
createdAt: number;
|
|
6
|
-
}
|
|
7
2
|
export declare function resolveWorkDirInput(baseDir: string, targetDir: string): string;
|
|
8
3
|
export declare class SessionManager {
|
|
9
4
|
private sessions;
|
|
@@ -24,15 +19,17 @@ export declare class SessionManager {
|
|
|
24
19
|
getWorkDir(userId: string): string;
|
|
25
20
|
hasUserSession(userId: string): boolean;
|
|
26
21
|
getConvId(userId: string): string;
|
|
27
|
-
setWorkDir(userId: string, workDir: string): Promise<
|
|
22
|
+
setWorkDir(userId: string, workDir: string): Promise<{
|
|
23
|
+
path: string;
|
|
24
|
+
resumed: boolean;
|
|
25
|
+
}>;
|
|
28
26
|
newSession(userId: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Set the active session to a specific SDK session (used by /resume).
|
|
29
|
+
* Bridges the SDK sessionId to our internal activeConvId tracking.
|
|
30
|
+
*/
|
|
31
|
+
setActiveSessionId(userId: string, sessionId: string): void;
|
|
29
32
|
clearActiveToolSession(userId: string, toolId: ToolId): boolean;
|
|
30
|
-
listConvHistory(userId: string): ConvHistoryEntry[];
|
|
31
|
-
getActiveConvInfo(userId: string): {
|
|
32
|
-
convId: string;
|
|
33
|
-
totalTurns: number;
|
|
34
|
-
} | undefined;
|
|
35
|
-
resumeConv(userId: string, convId: string): boolean;
|
|
36
33
|
addTurns(userId: string, turns: number): number;
|
|
37
34
|
addTurnsForThread(userId: string, threadId: string, turns: number): number;
|
|
38
35
|
getModel(userId: string, threadId?: string): string | undefined;
|
|
@@ -107,24 +107,17 @@ export class SessionManager {
|
|
|
107
107
|
const currentDir = this.getWorkDir(userId);
|
|
108
108
|
const realPath = await this.resolveAndValidate(currentDir, workDir);
|
|
109
109
|
const s = this.sessions.get(userId);
|
|
110
|
-
let oldConvId;
|
|
111
110
|
if (s) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!s.convHistory)
|
|
116
|
-
s.convHistory = [];
|
|
117
|
-
s.convHistory.push({
|
|
118
|
-
convId: oldConvId,
|
|
119
|
-
totalTurns: s.totalTurns ?? 0,
|
|
120
|
-
createdAt: Date.now(),
|
|
121
|
-
});
|
|
122
|
-
if (s.convHistory.length > 10)
|
|
123
|
-
s.convHistory = s.convHistory.slice(-10);
|
|
111
|
+
// Same directory — no-op
|
|
112
|
+
if (realPath === currentDir) {
|
|
113
|
+
return { path: realPath, resumed: false };
|
|
124
114
|
}
|
|
125
|
-
|
|
115
|
+
this.persistActiveConvSessions(userId, s);
|
|
116
|
+
// Clear active session IDs; SDK adapter will auto-resume latest CLI session for new dir
|
|
126
117
|
s.sessionIds = {};
|
|
127
118
|
s.activeConvId = randomBytes(4).toString('hex');
|
|
119
|
+
s.totalTurns = 0;
|
|
120
|
+
s.workDir = realPath;
|
|
128
121
|
}
|
|
129
122
|
else {
|
|
130
123
|
this.sessions.set(userId, {
|
|
@@ -133,8 +126,8 @@ export class SessionManager {
|
|
|
133
126
|
});
|
|
134
127
|
}
|
|
135
128
|
this.flushSync();
|
|
136
|
-
log.info(`WorkDir changed for user ${userId}: ${realPath}
|
|
137
|
-
return realPath;
|
|
129
|
+
log.info(`WorkDir changed for user ${userId}: ${realPath}`);
|
|
130
|
+
return { path: realPath, resumed: false };
|
|
138
131
|
}
|
|
139
132
|
newSession(userId) {
|
|
140
133
|
const s = this.sessions.get(userId);
|
|
@@ -142,19 +135,6 @@ export class SessionManager {
|
|
|
142
135
|
const oldSessionIds = { ...(s.sessionIds ?? {}) };
|
|
143
136
|
const oldConvId = s.activeConvId;
|
|
144
137
|
this.persistActiveConvSessions(userId, s);
|
|
145
|
-
// Archive old conversation to history (keep convSessionMap entries for resume)
|
|
146
|
-
if (oldConvId) {
|
|
147
|
-
if (!s.convHistory)
|
|
148
|
-
s.convHistory = [];
|
|
149
|
-
s.convHistory.push({
|
|
150
|
-
convId: oldConvId,
|
|
151
|
-
totalTurns: s.totalTurns ?? 0,
|
|
152
|
-
createdAt: Date.now(),
|
|
153
|
-
});
|
|
154
|
-
// Keep last 10 entries
|
|
155
|
-
if (s.convHistory.length > 10)
|
|
156
|
-
s.convHistory = s.convHistory.slice(-10);
|
|
157
|
-
}
|
|
158
138
|
s.sessionIds = {};
|
|
159
139
|
s.activeConvId = randomBytes(4).toString('hex');
|
|
160
140
|
s.totalTurns = 0;
|
|
@@ -164,6 +144,25 @@ export class SessionManager {
|
|
|
164
144
|
}
|
|
165
145
|
return false;
|
|
166
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Set the active session to a specific SDK session (used by /resume).
|
|
149
|
+
* Bridges the SDK sessionId to our internal activeConvId tracking.
|
|
150
|
+
*/
|
|
151
|
+
setActiveSessionId(userId, sessionId) {
|
|
152
|
+
let s = this.sessions.get(userId);
|
|
153
|
+
if (!s) {
|
|
154
|
+
s = { workDir: this.defaultWorkDir };
|
|
155
|
+
this.sessions.set(userId, s);
|
|
156
|
+
}
|
|
157
|
+
this.persistActiveConvSessions(userId, s);
|
|
158
|
+
s.activeConvId = randomBytes(4).toString('hex');
|
|
159
|
+
s.totalTurns = 0;
|
|
160
|
+
if (!s.sessionIds)
|
|
161
|
+
s.sessionIds = {};
|
|
162
|
+
s.sessionIds.claude = sessionId;
|
|
163
|
+
this.flushSync();
|
|
164
|
+
log.info(`Set active session for user ${userId}: sessionId=${sessionId}, convId=${s.activeConvId}`);
|
|
165
|
+
}
|
|
167
166
|
clearActiveToolSession(userId, toolId) {
|
|
168
167
|
const s = this.sessions.get(userId);
|
|
169
168
|
if (!s)
|
|
@@ -178,56 +177,6 @@ export class SessionManager {
|
|
|
178
177
|
log.info(`Cleared active ${toolId} session for user ${userId}, convId=${activeConvId ?? 'none'}`);
|
|
179
178
|
return hadSession;
|
|
180
179
|
}
|
|
181
|
-
listConvHistory(userId) {
|
|
182
|
-
const s = this.sessions.get(userId);
|
|
183
|
-
if (!s)
|
|
184
|
-
return [];
|
|
185
|
-
return s.convHistory ?? [];
|
|
186
|
-
}
|
|
187
|
-
getActiveConvInfo(userId) {
|
|
188
|
-
const s = this.sessions.get(userId);
|
|
189
|
-
if (!s?.activeConvId)
|
|
190
|
-
return undefined;
|
|
191
|
-
return { convId: s.activeConvId, totalTurns: s.totalTurns ?? 0 };
|
|
192
|
-
}
|
|
193
|
-
resumeConv(userId, convId) {
|
|
194
|
-
const s = this.sessions.get(userId);
|
|
195
|
-
if (!s)
|
|
196
|
-
return false;
|
|
197
|
-
// Find target in history
|
|
198
|
-
const idx = s.convHistory?.findIndex((e) => e.convId === convId) ?? -1;
|
|
199
|
-
if (idx === -1)
|
|
200
|
-
return false;
|
|
201
|
-
// Archive current active session
|
|
202
|
-
this.persistActiveConvSessions(userId, s);
|
|
203
|
-
if (s.activeConvId) {
|
|
204
|
-
if (!s.convHistory)
|
|
205
|
-
s.convHistory = [];
|
|
206
|
-
s.convHistory.push({
|
|
207
|
-
convId: s.activeConvId,
|
|
208
|
-
totalTurns: s.totalTurns ?? 0,
|
|
209
|
-
createdAt: Date.now(),
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
// Remove target from history
|
|
213
|
-
const [entry] = s.convHistory.splice(idx, 1);
|
|
214
|
-
// Restore target as active
|
|
215
|
-
s.activeConvId = entry.convId;
|
|
216
|
-
s.totalTurns = entry.totalTurns;
|
|
217
|
-
s.sessionIds = {};
|
|
218
|
-
// Restore sessionIds from convSessionMap
|
|
219
|
-
for (const toolId of ['claude', 'codex', 'codebuddy']) {
|
|
220
|
-
const sessionId = this.convSessionMap.get(this.getConvSessionKey(userId, entry.convId, toolId));
|
|
221
|
-
if (sessionId) {
|
|
222
|
-
if (!s.sessionIds)
|
|
223
|
-
s.sessionIds = {};
|
|
224
|
-
s.sessionIds[toolId] = sessionId;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
this.flushSync();
|
|
228
|
-
log.info(`Resumed session for user ${userId}: convId=${entry.convId}, turns=${entry.totalTurns}`);
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
180
|
addTurns(userId, turns) {
|
|
232
181
|
const s = this.sessions.get(userId);
|
|
233
182
|
if (!s)
|
|
@@ -314,6 +263,9 @@ export class SessionManager {
|
|
|
314
263
|
log.warn(`Legacy shared sessionId found for user ${k}; clearing it to avoid cross-tool resume conflicts`);
|
|
315
264
|
}
|
|
316
265
|
delete v.sessionId;
|
|
266
|
+
// Clean up legacy fields
|
|
267
|
+
delete v.firstMessage;
|
|
268
|
+
delete v.convHistory;
|
|
317
269
|
if (v.threads) {
|
|
318
270
|
for (const thread of Object.values(v.threads)) {
|
|
319
271
|
if (!thread.sessionIds)
|
package/dist/shared/ai-task.js
CHANGED