cc-viewer 1.6.294 → 1.6.296
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/cli.js +7 -2
- package/dist/assets/App-BeCGow-I.js +2 -0
- package/dist/assets/{MdxEditorPanel-B8xrlDZJ.js → MdxEditorPanel-D52b5qxi.js} +1 -1
- package/dist/assets/{Mobile-fsi8-Lpb.js → Mobile-8fflztx7.js} +1 -1
- package/dist/assets/index-DtpelJc4.js +2 -0
- package/dist/assets/seqResourceLoaders-DM-48tr-.js +2 -0
- package/dist/index.html +1 -1
- package/findcc.js +3 -3
- package/package.json +1 -1
- package/server/i18n.js +224 -8
- package/server/interceptor.js +23 -19
- package/server/lib/adapters/dingtalk-adapter.js +62 -0
- package/server/lib/adapters/discord-adapter.js +35 -0
- package/server/lib/adapters/feishu-adapter.js +37 -0
- package/server/lib/ask-store.js +19 -90
- package/server/lib/async-file-lock.js +123 -0
- package/server/lib/async-write-queue.js +131 -0
- package/server/lib/git-diff.js +4 -1
- package/server/lib/im-bridge-core.js +119 -14
- package/server/lib/im-config.js +11 -6
- package/server/lib/im-process-manager.js +1 -1
- package/server/lib/jsonl-archive.js +0 -1
- package/server/lib/log-management.js +46 -99
- package/server/lib/log-stream.js +102 -8
- package/server/lib/log-watcher.js +231 -178
- package/server/lib/plugin-manager.js +1 -1
- package/server/lib/updater.js +4 -2
- package/server/pty-manager.js +1 -1
- package/server/routes/ask-perm.js +2 -2
- package/server/routes/dingtalk.js +2 -0
- package/server/routes/events.js +3 -3
- package/server/routes/files-fs.js +4 -4
- package/server/routes/logs.js +5 -5
- package/server/routes/project-meta.js +18 -1
- package/server/routes/workspaces.js +10 -13
- package/server/server.js +33 -25
- package/server/workspace-registry.js +26 -72
- package/dist/assets/App-C66LoBEz.js +0 -2
- package/dist/assets/index-BTZqk5O5.js +0 -2
- package/dist/assets/seqResourceLoaders-6k4uXcNn.js +0 -2
package/dist/index.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// 整体显示大小已弃用 CSS zoom:Electron 改用 webFrame.setZoomFactor(首屏抢占见
|
|
22
22
|
// electron/tab-content-preload.js),纯浏览器交由用户用浏览器自带快捷键缩放,故此处不再设 zoom。
|
|
23
23
|
</script>
|
|
24
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
+
<script type="module" crossorigin src="/assets/index-DtpelJc4.js"></script>
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/vendor-antd-Bur5ZxWE.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-Si44UqBp.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-mdxeditor-Cco3AQJS.js">
|
package/findcc.js
CHANGED
|
@@ -105,7 +105,7 @@ export const LEGACY_INJECT_IMPORTS = [
|
|
|
105
105
|
|
|
106
106
|
export function getGlobalNodeModulesDir() {
|
|
107
107
|
try {
|
|
108
|
-
return execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
108
|
+
return execSync('npm root -g', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
109
109
|
} catch {
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
@@ -143,7 +143,7 @@ export function resolveNpmClaudePath() {
|
|
|
143
143
|
for (const cmd of lookupCmds) {
|
|
144
144
|
try {
|
|
145
145
|
// Windows `where` 输出可能多行 CRLF,取第一行 trim 即可
|
|
146
|
-
const rawOut = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env });
|
|
146
|
+
const rawOut = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env, windowsHide: true });
|
|
147
147
|
const result = rawOut.split(/\r?\n/)[0].trim();
|
|
148
148
|
if (result && existsSync(result)) {
|
|
149
149
|
// 只接受 npm 安装的符号链接(解析后指向 node_modules)
|
|
@@ -200,7 +200,7 @@ export function resolveNativePath() {
|
|
|
200
200
|
: [`which ${BINARY_NAME}`, `command -v ${BINARY_NAME}`];
|
|
201
201
|
for (const cmd of lookupCmds) {
|
|
202
202
|
try {
|
|
203
|
-
const rawOut = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env });
|
|
203
|
+
const rawOut = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env, windowsHide: true });
|
|
204
204
|
const result = rawOut.split(/\r?\n/)[0].trim();
|
|
205
205
|
if (result && existsSync(result)) {
|
|
206
206
|
// 只排除 .js 文件(老版本 npm 分发的 cli.js,需要 node 运行,
|
package/package.json
CHANGED
package/server/i18n.js
CHANGED
|
@@ -634,11 +634,13 @@ const i18nData = {
|
|
|
634
634
|
"server.dingtalk.noSession": { "zh": "当前没有活跃的 Claude 会话,无法处理。", "en": "No active Claude session — cannot process." },
|
|
635
635
|
"server.dingtalk.notBound": { "zh": "该会话未绑定到本机,已忽略。", "en": "This conversation is not bound; ignored." },
|
|
636
636
|
"server.dingtalk.notAuthorized": { "zh": "你不在允许的发送人白名单内。", "en": "You are not in the allowed sender list." },
|
|
637
|
-
"server.dingtalk.busyQueued": { "zh": "Claude
|
|
637
|
+
"server.dingtalk.busyQueued": { "zh": "Claude 正在忙,你的消息已排队(前方 {ahead} 条待处理,上限 {max})。", "en": "Claude is busy; your message is queued ({ahead} ahead, max {max})." },
|
|
638
638
|
"server.dingtalk.skipPermWarning": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
639
639
|
"server.dingtalk.skipPermBlocked": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
640
640
|
"server.dingtalk.injectFailed": { "zh": "注入失败(会话已结束或不可用),请确认 Claude 会话仍在运行。", "en": "Injection failed (session ended or unavailable) — check the Claude session is still running." },
|
|
641
|
-
"server.dingtalk.queueFull": { "zh": "
|
|
641
|
+
"server.dingtalk.queueFull": { "zh": "消息队列已满({max}),已丢弃该消息,请稍后再发。", "en": "Message queue is full ({max}); this message was dropped. Try again later." },
|
|
642
|
+
"server.dingtalk.ackProcessing": { "zh": "收到,正在思考…", "en": "Got it, thinking…" },
|
|
643
|
+
"server.dingtalk.ackTimeout": { "zh": "处理超时,请重试。", "en": "Processing timed out, please try again." },
|
|
642
644
|
"server.feishu.replyChunkTitle": { "zh": "Claude", "en": "Claude" },
|
|
643
645
|
"server.feishu.truncated": { "zh": "(回复过长已截断,完整内容请见 Web 界面)", "en": "(reply truncated — see the web UI for the full content)" },
|
|
644
646
|
"server.feishu.noTextReply": { "zh": "(本轮无文本回复)", "en": "(no text reply this turn)" },
|
|
@@ -646,11 +648,13 @@ const i18nData = {
|
|
|
646
648
|
"server.feishu.noSession": { "zh": "当前没有活跃的 Claude 会话,无法处理。", "en": "No active Claude session — cannot process." },
|
|
647
649
|
"server.feishu.notBound": { "zh": "该会话未绑定到本机,已忽略。", "en": "This conversation is not bound; ignored." },
|
|
648
650
|
"server.feishu.notAuthorized": { "zh": "你不在允许的发送人白名单内。", "en": "You are not in the allowed sender list." },
|
|
649
|
-
"server.feishu.busyQueued": { "zh": "Claude
|
|
651
|
+
"server.feishu.busyQueued": { "zh": "Claude 正在忙,你的消息已排队(前方 {ahead} 条待处理,上限 {max})。", "en": "Claude is busy; your message is queued ({ahead} ahead, max {max})." },
|
|
650
652
|
"server.feishu.skipPermWarning": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
651
653
|
"server.feishu.skipPermBlocked": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
652
654
|
"server.feishu.injectFailed": { "zh": "注入失败(会话已结束或不可用),请确认 Claude 会话仍在运行。", "en": "Injection failed (session ended or unavailable) — check the Claude session is still running." },
|
|
653
|
-
"server.feishu.queueFull": { "zh": "
|
|
655
|
+
"server.feishu.queueFull": { "zh": "消息队列已满({max}),已丢弃该消息,请稍后再发。", "en": "Message queue is full ({max}); this message was dropped. Try again later." },
|
|
656
|
+
"server.feishu.ackProcessing": { "zh": "收到,正在思考…", "en": "Got it, thinking…" },
|
|
657
|
+
"server.feishu.ackTimeout": { "zh": "处理超时,请重试。", "en": "Processing timed out, please try again." },
|
|
654
658
|
"server.wecom.replyChunkTitle": { "zh": "Claude", "en": "Claude" },
|
|
655
659
|
"server.wecom.truncated": { "zh": "(回复过长已截断,完整内容请见 Web 界面)", "en": "(reply truncated — see the web UI for the full content)" },
|
|
656
660
|
"server.wecom.noTextReply": { "zh": "(本轮无文本回复)", "en": "(no text reply this turn)" },
|
|
@@ -658,11 +662,13 @@ const i18nData = {
|
|
|
658
662
|
"server.wecom.noSession": { "zh": "当前没有活跃的 Claude 会话,无法处理。", "en": "No active Claude session — cannot process." },
|
|
659
663
|
"server.wecom.notBound": { "zh": "该会话未绑定到本机,已忽略。", "en": "This conversation is not bound; ignored." },
|
|
660
664
|
"server.wecom.notAuthorized": { "zh": "你不在允许的发送人白名单内。", "en": "You are not in the allowed sender list." },
|
|
661
|
-
"server.wecom.busyQueued": { "zh": "Claude
|
|
665
|
+
"server.wecom.busyQueued": { "zh": "Claude 正在忙,你的消息已排队(前方 {ahead} 条待处理,上限 {max})。", "en": "Claude is busy; your message is queued ({ahead} ahead, max {max})." },
|
|
662
666
|
"server.wecom.skipPermWarning": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
663
667
|
"server.wecom.skipPermBlocked": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
664
668
|
"server.wecom.injectFailed": { "zh": "注入失败(会话已结束或不可用),请确认 Claude 会话仍在运行。", "en": "Injection failed (session ended or unavailable) — check the Claude session is still running." },
|
|
665
|
-
"server.wecom.queueFull": { "zh": "
|
|
669
|
+
"server.wecom.queueFull": { "zh": "消息队列已满({max}),已丢弃该消息,请稍后再发。", "en": "Message queue is full ({max}); this message was dropped. Try again later." },
|
|
670
|
+
"server.wecom.ackProcessing": { "zh": "收到,正在思考…", "en": "Got it, thinking…" },
|
|
671
|
+
"server.wecom.ackTimeout": { "zh": "处理超时,请重试。", "en": "Processing timed out, please try again." },
|
|
666
672
|
"server.discord.replyChunkTitle": { "zh": "Claude", "en": "Claude" },
|
|
667
673
|
"server.discord.truncated": { "zh": "(回复过长已截断,完整内容请见 Web 界面)", "en": "(reply truncated — see the web UI for the full content)" },
|
|
668
674
|
"server.discord.noTextReply": { "zh": "(本轮无文本回复)", "en": "(no text reply this turn)" },
|
|
@@ -670,11 +676,13 @@ const i18nData = {
|
|
|
670
676
|
"server.discord.noSession": { "zh": "当前没有活跃的 Claude 会话,无法处理。", "en": "No active Claude session — cannot process." },
|
|
671
677
|
"server.discord.notBound": { "zh": "该会话未绑定到本机,已忽略。", "en": "This conversation is not bound; ignored." },
|
|
672
678
|
"server.discord.notAuthorized": { "zh": "你不在允许的发送人白名单内。", "en": "You are not in the allowed sender list." },
|
|
673
|
-
"server.discord.busyQueued": { "zh": "Claude
|
|
679
|
+
"server.discord.busyQueued": { "zh": "Claude 正在忙,你的消息已排队(前方 {ahead} 条待处理,上限 {max})。", "en": "Claude is busy; your message is queued ({ahead} ahead, max {max})." },
|
|
674
680
|
"server.discord.skipPermWarning": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
675
681
|
"server.discord.skipPermBlocked": { "zh": "收到,正在思考", "en": "Got it, thinking…" },
|
|
676
682
|
"server.discord.injectFailed": { "zh": "注入失败(会话已结束或不可用),请确认 Claude 会话仍在运行。", "en": "Injection failed (session ended or unavailable) — check the Claude session is still running." },
|
|
677
|
-
"server.discord.queueFull": { "zh": "
|
|
683
|
+
"server.discord.queueFull": { "zh": "消息队列已满({max}),已丢弃该消息,请稍后再发。", "en": "Message queue is full ({max}); this message was dropped. Try again later." },
|
|
684
|
+
"server.discord.ackProcessing": { "zh": "收到,正在思考…", "en": "Got it, thinking…" },
|
|
685
|
+
"server.discord.ackTimeout": { "zh": "处理超时,请重试。", "en": "Processing timed out, please try again." },
|
|
678
686
|
"update.updating": {
|
|
679
687
|
"zh": "正在更新到 v{version}...",
|
|
680
688
|
"en": "Updating to v{version}...",
|
|
@@ -2639,6 +2647,214 @@ const i18nData = {
|
|
|
2639
2647
|
"ar": "سؤال في {project}", "no": "Spørsmål i {project}", "pt-BR": "Pergunta em {project}",
|
|
2640
2648
|
"th": "คำถามใน {project}", "tr": "{project} içinde soru", "uk": "Питання у {project}"
|
|
2641
2649
|
},
|
|
2650
|
+
"electron.menu.file": {
|
|
2651
|
+
"zh": "文件", "en": "File", "zh-TW": "檔案",
|
|
2652
|
+
"ko": "파일", "ja": "ファイル", "de": "Datei",
|
|
2653
|
+
"es": "Archivo", "fr": "Fichier", "it": "File",
|
|
2654
|
+
"da": "Fil", "pl": "Plik", "ru": "Файл",
|
|
2655
|
+
"ar": "ملف", "no": "Fil", "pt-BR": "Arquivo",
|
|
2656
|
+
"th": "ไฟล์", "tr": "Dosya", "uk": "Файл"
|
|
2657
|
+
},
|
|
2658
|
+
"electron.menu.edit": {
|
|
2659
|
+
"zh": "编辑", "en": "Edit", "zh-TW": "編輯",
|
|
2660
|
+
"ko": "편집", "ja": "編集", "de": "Bearbeiten",
|
|
2661
|
+
"es": "Editar", "fr": "Édition", "it": "Modifica",
|
|
2662
|
+
"da": "Rediger", "pl": "Edycja", "ru": "Правка",
|
|
2663
|
+
"ar": "تحرير", "no": "Rediger", "pt-BR": "Editar",
|
|
2664
|
+
"th": "แก้ไข", "tr": "Düzen", "uk": "Редагувати"
|
|
2665
|
+
},
|
|
2666
|
+
"electron.menu.view": {
|
|
2667
|
+
"zh": "视图", "en": "View", "zh-TW": "檢視",
|
|
2668
|
+
"ko": "보기", "ja": "表示", "de": "Ansicht",
|
|
2669
|
+
"es": "Ver", "fr": "Affichage", "it": "Visualizza",
|
|
2670
|
+
"da": "Vis", "pl": "Widok", "ru": "Вид",
|
|
2671
|
+
"ar": "عرض", "no": "Vis", "pt-BR": "Exibir",
|
|
2672
|
+
"th": "มุมมอง", "tr": "Görünüm", "uk": "Вигляд"
|
|
2673
|
+
},
|
|
2674
|
+
"electron.menu.window": {
|
|
2675
|
+
"zh": "窗口", "en": "Window", "zh-TW": "視窗",
|
|
2676
|
+
"ko": "창", "ja": "ウィンドウ", "de": "Fenster",
|
|
2677
|
+
"es": "Ventana", "fr": "Fenêtre", "it": "Finestra",
|
|
2678
|
+
"da": "Vindue", "pl": "Okno", "ru": "Окно",
|
|
2679
|
+
"ar": "نافذة", "no": "Vindu", "pt-BR": "Janela",
|
|
2680
|
+
"th": "หน้าต่าง", "tr": "Pencere", "uk": "Вікно"
|
|
2681
|
+
},
|
|
2682
|
+
"electron.menu.newTab": {
|
|
2683
|
+
"zh": "新建标签页", "en": "New Tab", "zh-TW": "新增分頁",
|
|
2684
|
+
"ko": "새 탭", "ja": "新しいタブ", "de": "Neuer Tab",
|
|
2685
|
+
"es": "Nueva pestaña", "fr": "Nouvel onglet", "it": "Nuova scheda",
|
|
2686
|
+
"da": "Ny fane", "pl": "Nowa karta", "ru": "Новая вкладка",
|
|
2687
|
+
"ar": "علامة تبويب جديدة", "no": "Ny fane", "pt-BR": "Nova aba",
|
|
2688
|
+
"th": "แท็บใหม่", "tr": "Yeni Sekme", "uk": "Нова вкладка"
|
|
2689
|
+
},
|
|
2690
|
+
"electron.menu.closeTab": {
|
|
2691
|
+
"zh": "关闭标签页", "en": "Close Tab", "zh-TW": "關閉分頁",
|
|
2692
|
+
"ko": "탭 닫기", "ja": "タブを閉じる", "de": "Tab schließen",
|
|
2693
|
+
"es": "Cerrar pestaña", "fr": "Fermer l'onglet", "it": "Chiudi scheda",
|
|
2694
|
+
"da": "Luk fane", "pl": "Zamknij kartę", "ru": "Закрыть вкладку",
|
|
2695
|
+
"ar": "إغلاق علامة التبويب", "no": "Lukk fane", "pt-BR": "Fechar aba",
|
|
2696
|
+
"th": "ปิดแท็บ", "tr": "Sekmeyi Kapat", "uk": "Закрити вкладку"
|
|
2697
|
+
},
|
|
2698
|
+
"electron.menu.undo": {
|
|
2699
|
+
"zh": "撤销", "en": "Undo", "zh-TW": "復原",
|
|
2700
|
+
"ko": "실행 취소", "ja": "元に戻す", "de": "Rückgängig",
|
|
2701
|
+
"es": "Deshacer", "fr": "Annuler", "it": "Annulla",
|
|
2702
|
+
"da": "Fortryd", "pl": "Cofnij", "ru": "Отменить",
|
|
2703
|
+
"ar": "تراجع", "no": "Angre", "pt-BR": "Desfazer",
|
|
2704
|
+
"th": "เลิกทำ", "tr": "Geri Al", "uk": "Скасувати"
|
|
2705
|
+
},
|
|
2706
|
+
"electron.menu.redo": {
|
|
2707
|
+
"zh": "重做", "en": "Redo", "zh-TW": "重做",
|
|
2708
|
+
"ko": "다시 실행", "ja": "やり直す", "de": "Wiederholen",
|
|
2709
|
+
"es": "Rehacer", "fr": "Rétablir", "it": "Ripeti",
|
|
2710
|
+
"da": "Gentag", "pl": "Ponów", "ru": "Повторить",
|
|
2711
|
+
"ar": "إعادة", "no": "Gjør om", "pt-BR": "Refazer",
|
|
2712
|
+
"th": "ทำซ้ำ", "tr": "Yinele", "uk": "Повторити"
|
|
2713
|
+
},
|
|
2714
|
+
"electron.menu.cut": {
|
|
2715
|
+
"zh": "剪切", "en": "Cut", "zh-TW": "剪下",
|
|
2716
|
+
"ko": "잘라내기", "ja": "切り取り", "de": "Ausschneiden",
|
|
2717
|
+
"es": "Cortar", "fr": "Couper", "it": "Taglia",
|
|
2718
|
+
"da": "Klip", "pl": "Wytnij", "ru": "Вырезать",
|
|
2719
|
+
"ar": "قص", "no": "Klipp ut", "pt-BR": "Recortar",
|
|
2720
|
+
"th": "ตัด", "tr": "Kes", "uk": "Вирізати"
|
|
2721
|
+
},
|
|
2722
|
+
"electron.menu.copy": {
|
|
2723
|
+
"zh": "复制", "en": "Copy", "zh-TW": "複製",
|
|
2724
|
+
"ko": "복사", "ja": "コピー", "de": "Kopieren",
|
|
2725
|
+
"es": "Copiar", "fr": "Copier", "it": "Copia",
|
|
2726
|
+
"da": "Kopiér", "pl": "Kopiuj", "ru": "Копировать",
|
|
2727
|
+
"ar": "نسخ", "no": "Kopier", "pt-BR": "Copiar",
|
|
2728
|
+
"th": "คัดลอก", "tr": "Kopyala", "uk": "Копіювати"
|
|
2729
|
+
},
|
|
2730
|
+
"electron.menu.paste": {
|
|
2731
|
+
"zh": "粘贴", "en": "Paste", "zh-TW": "貼上",
|
|
2732
|
+
"ko": "붙여넣기", "ja": "貼り付け", "de": "Einfügen",
|
|
2733
|
+
"es": "Pegar", "fr": "Coller", "it": "Incolla",
|
|
2734
|
+
"da": "Sæt ind", "pl": "Wklej", "ru": "Вставить",
|
|
2735
|
+
"ar": "لصق", "no": "Lim inn", "pt-BR": "Colar",
|
|
2736
|
+
"th": "วาง", "tr": "Yapıştır", "uk": "Вставити"
|
|
2737
|
+
},
|
|
2738
|
+
"electron.menu.selectAll": {
|
|
2739
|
+
"zh": "全选", "en": "Select All", "zh-TW": "全選",
|
|
2740
|
+
"ko": "모두 선택", "ja": "すべて選択", "de": "Alles auswählen",
|
|
2741
|
+
"es": "Seleccionar todo", "fr": "Tout sélectionner", "it": "Seleziona tutto",
|
|
2742
|
+
"da": "Vælg alt", "pl": "Zaznacz wszystko", "ru": "Выделить всё",
|
|
2743
|
+
"ar": "تحديد الكل", "no": "Velg alt", "pt-BR": "Selecionar tudo",
|
|
2744
|
+
"th": "เลือกทั้งหมด", "tr": "Tümünü Seç", "uk": "Виділити все"
|
|
2745
|
+
},
|
|
2746
|
+
"electron.menu.reload": {
|
|
2747
|
+
"zh": "重新加载", "en": "Reload", "zh-TW": "重新載入",
|
|
2748
|
+
"ko": "새로 고침", "ja": "再読み込み", "de": "Neu laden",
|
|
2749
|
+
"es": "Recargar", "fr": "Recharger", "it": "Ricarica",
|
|
2750
|
+
"da": "Genindlæs", "pl": "Załaduj ponownie", "ru": "Перезагрузить",
|
|
2751
|
+
"ar": "إعادة تحميل", "no": "Last inn på nytt", "pt-BR": "Recarregar",
|
|
2752
|
+
"th": "โหลดใหม่", "tr": "Yeniden Yükle", "uk": "Перезавантажити"
|
|
2753
|
+
},
|
|
2754
|
+
"electron.menu.forceReload": {
|
|
2755
|
+
"zh": "强制重新加载", "en": "Force Reload", "zh-TW": "強制重新載入",
|
|
2756
|
+
"ko": "강제 새로 고침", "ja": "強制再読み込み", "de": "Erzwungenes Neuladen",
|
|
2757
|
+
"es": "Forzar recarga", "fr": "Forcer le rechargement", "it": "Forza ricarica",
|
|
2758
|
+
"da": "Tving genindlæsning", "pl": "Wymuś ponowne załadowanie", "ru": "Принудительно перезагрузить",
|
|
2759
|
+
"ar": "فرض إعادة التحميل", "no": "Tving omlasting", "pt-BR": "Forçar recarregamento",
|
|
2760
|
+
"th": "บังคับโหลดใหม่", "tr": "Zorla Yeniden Yükle", "uk": "Примусово перезавантажити"
|
|
2761
|
+
},
|
|
2762
|
+
"electron.menu.toggleDevTools": {
|
|
2763
|
+
"zh": "开发者工具", "en": "Developer Tools", "zh-TW": "開發人員工具",
|
|
2764
|
+
"ko": "개발자 도구", "ja": "開発者ツール", "de": "Entwicklertools",
|
|
2765
|
+
"es": "Herramientas de desarrollo", "fr": "Outils de développement", "it": "Strumenti di sviluppo",
|
|
2766
|
+
"da": "Udviklerværktøjer", "pl": "Narzędzia deweloperskie", "ru": "Инструменты разработчика",
|
|
2767
|
+
"ar": "أدوات المطور", "no": "Utviklerverktøy", "pt-BR": "Ferramentas do desenvolvedor",
|
|
2768
|
+
"th": "เครื่องมือนักพัฒนา", "tr": "Geliştirici Araçları", "uk": "Інструменти розробника"
|
|
2769
|
+
},
|
|
2770
|
+
"electron.menu.fullscreen": {
|
|
2771
|
+
"zh": "切换全屏", "en": "Toggle Full Screen", "zh-TW": "切換全螢幕",
|
|
2772
|
+
"ko": "전체 화면 전환", "ja": "フルスクリーン切替", "de": "Vollbild umschalten",
|
|
2773
|
+
"es": "Alternar pantalla completa", "fr": "Basculer en plein écran", "it": "Schermo intero",
|
|
2774
|
+
"da": "Skift fuld skærm", "pl": "Przełącz pełny ekran", "ru": "Полноэкранный режим",
|
|
2775
|
+
"ar": "ملء الشاشة", "no": "Veksle fullskjerm", "pt-BR": "Alternar tela cheia",
|
|
2776
|
+
"th": "สลับเต็มหน้าจอ", "tr": "Tam Ekran", "uk": "Повноекранний режим"
|
|
2777
|
+
},
|
|
2778
|
+
"electron.menu.minimize": {
|
|
2779
|
+
"zh": "最小化", "en": "Minimize", "zh-TW": "最小化",
|
|
2780
|
+
"ko": "최소화", "ja": "最小化", "de": "Minimieren",
|
|
2781
|
+
"es": "Minimizar", "fr": "Réduire", "it": "Riduci a icona",
|
|
2782
|
+
"da": "Minimér", "pl": "Minimalizuj", "ru": "Свернуть",
|
|
2783
|
+
"ar": "تصغير", "no": "Minimer", "pt-BR": "Minimizar",
|
|
2784
|
+
"th": "ย่อเล็กสุด", "tr": "Simge Durumuna Küçült", "uk": "Згорнути"
|
|
2785
|
+
},
|
|
2786
|
+
"electron.menu.maximize": {
|
|
2787
|
+
"zh": "最大化", "en": "Maximize", "zh-TW": "最大化",
|
|
2788
|
+
"ko": "최대화", "ja": "最大化", "de": "Maximieren",
|
|
2789
|
+
"es": "Maximizar", "fr": "Agrandir", "it": "Ingrandisci",
|
|
2790
|
+
"da": "Maksimér", "pl": "Maksymalizuj", "ru": "Развернуть",
|
|
2791
|
+
"ar": "تكبير", "no": "Maksimer", "pt-BR": "Maximizar",
|
|
2792
|
+
"th": "ขยายใหญ่สุด", "tr": "Ekranı Kapla", "uk": "Розгорнути"
|
|
2793
|
+
},
|
|
2794
|
+
"electron.menu.close": {
|
|
2795
|
+
"zh": "关闭窗口", "en": "Close Window", "zh-TW": "關閉視窗",
|
|
2796
|
+
"ko": "창 닫기", "ja": "ウィンドウを閉じる", "de": "Fenster schließen",
|
|
2797
|
+
"es": "Cerrar ventana", "fr": "Fermer la fenêtre", "it": "Chiudi finestra",
|
|
2798
|
+
"da": "Luk vindue", "pl": "Zamknij okno", "ru": "Закрыть окно",
|
|
2799
|
+
"ar": "إغلاق النافذة", "no": "Lukk vindu", "pt-BR": "Fechar janela",
|
|
2800
|
+
"th": "ปิดหน้าต่าง", "tr": "Pencereyi Kapat", "uk": "Закрити вікно"
|
|
2801
|
+
},
|
|
2802
|
+
"electron.menu.prevTab": {
|
|
2803
|
+
"zh": "上一个标签页", "en": "Previous Tab", "zh-TW": "上一個分頁",
|
|
2804
|
+
"ko": "이전 탭", "ja": "前のタブ", "de": "Vorheriger Tab",
|
|
2805
|
+
"es": "Pestaña anterior", "fr": "Onglet précédent", "it": "Scheda precedente",
|
|
2806
|
+
"da": "Forrige fane", "pl": "Poprzednia karta", "ru": "Предыдущая вкладка",
|
|
2807
|
+
"ar": "علامة التبويب السابقة", "no": "Forrige fane", "pt-BR": "Aba anterior",
|
|
2808
|
+
"th": "แท็บก่อนหน้า", "tr": "Önceki Sekme", "uk": "Попередня вкладка"
|
|
2809
|
+
},
|
|
2810
|
+
"electron.menu.nextTab": {
|
|
2811
|
+
"zh": "下一个标签页", "en": "Next Tab", "zh-TW": "下一個分頁",
|
|
2812
|
+
"ko": "다음 탭", "ja": "次のタブ", "de": "Nächster Tab",
|
|
2813
|
+
"es": "Pestaña siguiente", "fr": "Onglet suivant", "it": "Scheda successiva",
|
|
2814
|
+
"da": "Næste fane", "pl": "Następna karta", "ru": "Следующая вкладка",
|
|
2815
|
+
"ar": "علامة التبويب التالية", "no": "Neste fane", "pt-BR": "Próxima aba",
|
|
2816
|
+
"th": "แท็บถัดไป", "tr": "Sonraki Sekme", "uk": "Наступна вкладка"
|
|
2817
|
+
},
|
|
2818
|
+
"electron.menu.copyLink": {
|
|
2819
|
+
"zh": "复制链接地址", "en": "Copy Link Address", "zh-TW": "複製連結網址",
|
|
2820
|
+
"ko": "링크 주소 복사", "ja": "リンクアドレスをコピー", "de": "Linkadresse kopieren",
|
|
2821
|
+
"es": "Copiar dirección del enlace", "fr": "Copier l'adresse du lien", "it": "Copia indirizzo link",
|
|
2822
|
+
"da": "Kopiér linkadresse", "pl": "Kopiuj adres linku", "ru": "Копировать адрес ссылки",
|
|
2823
|
+
"ar": "نسخ عنوان الرابط", "no": "Kopier lenkeadresse", "pt-BR": "Copiar endereço do link",
|
|
2824
|
+
"th": "คัดลอกที่อยู่ลิงก์", "tr": "Bağlantı Adresini Kopyala", "uk": "Копіювати адресу посилання"
|
|
2825
|
+
},
|
|
2826
|
+
"electron.tabbar.newTab": {
|
|
2827
|
+
"zh": "新建项目标签页", "en": "New project tab", "zh-TW": "新增專案分頁",
|
|
2828
|
+
"ko": "새 프로젝트 탭", "ja": "新しいプロジェクトタブ", "de": "Neuer Projekt-Tab",
|
|
2829
|
+
"es": "Nueva pestaña de proyecto", "fr": "Nouvel onglet de projet", "it": "Nuova scheda progetto",
|
|
2830
|
+
"da": "Ny projektfane", "pl": "Nowa karta projektu", "ru": "Новая вкладка проекта",
|
|
2831
|
+
"ar": "علامة تبويب مشروع جديدة", "no": "Ny prosjektfane", "pt-BR": "Nova aba de projeto",
|
|
2832
|
+
"th": "แท็บโปรเจกต์ใหม่", "tr": "Yeni Proje Sekmesi", "uk": "Нова вкладка проєкту"
|
|
2833
|
+
},
|
|
2834
|
+
"electron.tabbar.toIpad": {
|
|
2835
|
+
"zh": "切换到 iPad 模式", "en": "Switch to iPad mode", "zh-TW": "切換到 iPad 模式",
|
|
2836
|
+
"ko": "iPad 모드로 전환", "ja": "iPad モードに切替", "de": "Zu iPad-Modus wechseln",
|
|
2837
|
+
"es": "Cambiar a modo iPad", "fr": "Passer en mode iPad", "it": "Passa alla modalità iPad",
|
|
2838
|
+
"da": "Skift til iPad-tilstand", "pl": "Przełącz na tryb iPad", "ru": "Переключиться в режим iPad",
|
|
2839
|
+
"ar": "التبديل إلى وضع iPad", "no": "Bytt til iPad-modus", "pt-BR": "Mudar para modo iPad",
|
|
2840
|
+
"th": "สลับเป็นโหมด iPad", "tr": "iPad moduna geç", "uk": "Перейти в режим iPad"
|
|
2841
|
+
},
|
|
2842
|
+
"electron.tabbar.toPc": {
|
|
2843
|
+
"zh": "切换到 PC 模式", "en": "Switch to PC mode", "zh-TW": "切換到 PC 模式",
|
|
2844
|
+
"ko": "PC 모드로 전환", "ja": "PC モードに切替", "de": "Zu PC-Modus wechseln",
|
|
2845
|
+
"es": "Cambiar a modo PC", "fr": "Passer en mode PC", "it": "Passa alla modalità PC",
|
|
2846
|
+
"da": "Skift til PC-tilstand", "pl": "Przełącz na tryb PC", "ru": "Переключиться в режим ПК",
|
|
2847
|
+
"ar": "التبديل إلى وضع PC", "no": "Bytt til PC-modus", "pt-BR": "Mudar para modo PC",
|
|
2848
|
+
"th": "สลับเป็นโหมด PC", "tr": "PC moduna geç", "uk": "Перейти в режим ПК"
|
|
2849
|
+
},
|
|
2850
|
+
"electron.tabbar.menu": {
|
|
2851
|
+
"zh": "菜单", "en": "Menu", "zh-TW": "選單",
|
|
2852
|
+
"ko": "메뉴", "ja": "メニュー", "de": "Menü",
|
|
2853
|
+
"es": "Menú", "fr": "Menu", "it": "Menu",
|
|
2854
|
+
"da": "Menu", "pl": "Menu", "ru": "Меню",
|
|
2855
|
+
"ar": "قائمة", "no": "Meny", "pt-BR": "Menu",
|
|
2856
|
+
"th": "เมนู", "tr": "Menü", "uk": "Меню"
|
|
2857
|
+
},
|
|
2642
2858
|
"cli.userNameRequired": {
|
|
2643
2859
|
"zh": "错误: --user-name 需要一个名称参数",
|
|
2644
2860
|
"en": "Error: --user-name requires a name argument",
|
package/server/interceptor.js
CHANGED
|
@@ -8,6 +8,7 @@ const _ccvSkip = _ccvSkipArgs.includes(process.argv[2]);
|
|
|
8
8
|
|
|
9
9
|
import './lib/proxy-env.js';
|
|
10
10
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync, statSync, unlinkSync, existsSync, watchFile } from 'node:fs';
|
|
11
|
+
import { AsyncWriteQueue } from './lib/async-write-queue.js';
|
|
11
12
|
import { renameSyncWithRetry } from './lib/file-api.js';
|
|
12
13
|
import http from 'node:http';
|
|
13
14
|
import https from 'node:https';
|
|
@@ -322,6 +323,9 @@ if (process.env.CCV_WORKSPACE_MODE === '1') {
|
|
|
322
323
|
}
|
|
323
324
|
let LOG_FILE = _newLogFile;
|
|
324
325
|
|
|
326
|
+
// 异步写入队列 — 替代 appendFileSync,避免阻塞事件循环(Windows NTFS 尤为严重)
|
|
327
|
+
const _writeQueue = new AsyncWriteQueue(() => LOG_FILE);
|
|
328
|
+
|
|
325
329
|
// 现在 _projectName/_logDir 已初始化,可以安全加载 proxy profile(含 workspace override)
|
|
326
330
|
// 并挂载 watchFile 同步列表变化。
|
|
327
331
|
_loadProxyProfile();
|
|
@@ -403,14 +407,16 @@ export function resetWorkspace() {
|
|
|
403
407
|
_loadProxyProfile(); // workspace 上下文消失,回落到 profile.json.active
|
|
404
408
|
}
|
|
405
409
|
|
|
406
|
-
|
|
410
|
+
// Windows NTFS + Defender 下大文件 I/O 代价远高于 Mac/Linux,降低分割阈值减轻压力
|
|
411
|
+
const MAX_LOG_SIZE = (process.platform === 'win32' ? 150 : 300) * 1024 * 1024;
|
|
407
412
|
|
|
408
|
-
function checkAndRotateLogFile() {
|
|
413
|
+
async function checkAndRotateLogFile() {
|
|
409
414
|
// Teammate 不做日志轮转,由 leader 负责
|
|
410
415
|
if (_isTeammate) return;
|
|
411
416
|
try {
|
|
412
417
|
if (!existsSync(LOG_FILE) || statSync(LOG_FILE).size < MAX_LOG_SIZE) return;
|
|
413
418
|
} catch { return; }
|
|
419
|
+
await _writeQueue.flush();
|
|
414
420
|
const { filePath } = generateNewLogFilePath();
|
|
415
421
|
const result = rotateLogFile(LOG_FILE, filePath, MAX_LOG_SIZE);
|
|
416
422
|
if (result.rotated) {
|
|
@@ -507,15 +513,15 @@ export function setupInterceptor() {
|
|
|
507
513
|
};
|
|
508
514
|
|
|
509
515
|
process.on('SIGINT', () => {
|
|
510
|
-
cleanupViewer().finally(() => process.exit(0));
|
|
516
|
+
_writeQueue.close().then(() => cleanupViewer()).finally(() => process.exit(0));
|
|
511
517
|
});
|
|
512
518
|
|
|
513
519
|
process.on('SIGTERM', () => {
|
|
514
|
-
cleanupViewer().finally(() => process.exit(0));
|
|
520
|
+
_writeQueue.close().then(() => cleanupViewer()).finally(() => process.exit(0));
|
|
515
521
|
});
|
|
516
522
|
|
|
517
523
|
process.on('beforeExit', () => {
|
|
518
|
-
cleanupViewer();
|
|
524
|
+
_writeQueue.close().then(() => cleanupViewer());
|
|
519
525
|
});
|
|
520
526
|
|
|
521
527
|
const _originalFetch = globalThis.fetch;
|
|
@@ -627,7 +633,7 @@ export function setupInterceptor() {
|
|
|
627
633
|
|
|
628
634
|
// 用户新指令边界:检查日志文件大小,超过 250MB 则切换新文件
|
|
629
635
|
if (requestEntry?.mainAgent) {
|
|
630
|
-
checkAndRotateLogFile();
|
|
636
|
+
await checkAndRotateLogFile();
|
|
631
637
|
// 仅 mainAgent 请求时缓存模型名,避免 SubAgent 覆盖
|
|
632
638
|
if (requestEntry.body?.model && typeof requestEntry.body.model === 'string') {
|
|
633
639
|
_cachedModel = requestEntry.body.model;
|
|
@@ -719,9 +725,7 @@ export function setupInterceptor() {
|
|
|
719
725
|
if (requestEntry) {
|
|
720
726
|
const willLiveStream = !!_livePort && requestEntry.mainAgent && !_isTeammate;
|
|
721
727
|
if (!willLiveStream) {
|
|
722
|
-
|
|
723
|
-
appendFileSync(LOG_FILE, JSON.stringify(requestEntry) + '\n---\n');
|
|
724
|
-
} catch { }
|
|
728
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n');
|
|
725
729
|
}
|
|
726
730
|
}
|
|
727
731
|
|
|
@@ -924,8 +928,8 @@ export function setupInterceptor() {
|
|
|
924
928
|
// 移除在途请求标记,保持原始报文
|
|
925
929
|
delete requestEntry.inProgress;
|
|
926
930
|
delete requestEntry.requestId;
|
|
927
|
-
|
|
928
|
-
|
|
931
|
+
{ const _dl = _deltaOriginalMessagesLength, _tf = _deltaOriginalTailFp;
|
|
932
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n', () => _commitDeltaState(_dl, _tf)); }
|
|
929
933
|
// Release memory: clear large objects after disk write
|
|
930
934
|
streamedChunks = [];
|
|
931
935
|
streamedContentLen = 0;
|
|
@@ -935,8 +939,8 @@ export function setupInterceptor() {
|
|
|
935
939
|
requestEntry.response.body = fullContent.slice(0, 1000);
|
|
936
940
|
delete requestEntry.inProgress;
|
|
937
941
|
delete requestEntry.requestId;
|
|
938
|
-
|
|
939
|
-
|
|
942
|
+
{ const _dl = _deltaOriginalMessagesLength, _tf = _deltaOriginalTailFp;
|
|
943
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n', () => _commitDeltaState(_dl, _tf)); }
|
|
940
944
|
streamedChunks = [];
|
|
941
945
|
streamedContentLen = 0;
|
|
942
946
|
requestEntry.response = null;
|
|
@@ -1005,8 +1009,8 @@ export function setupInterceptor() {
|
|
|
1005
1009
|
};
|
|
1006
1010
|
delete requestEntry.inProgress;
|
|
1007
1011
|
delete requestEntry.requestId;
|
|
1008
|
-
|
|
1009
|
-
|
|
1012
|
+
{ const _dl = _deltaOriginalMessagesLength, _tf = _deltaOriginalTailFp;
|
|
1013
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n', () => _commitDeltaState(_dl, _tf)); }
|
|
1010
1014
|
resetStreamingState();
|
|
1011
1015
|
}
|
|
1012
1016
|
} else {
|
|
@@ -1033,13 +1037,13 @@ export function setupInterceptor() {
|
|
|
1033
1037
|
delete requestEntry.inProgress;
|
|
1034
1038
|
delete requestEntry.requestId;
|
|
1035
1039
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1040
|
+
{ const _dl = _deltaOriginalMessagesLength, _tf = _deltaOriginalTailFp;
|
|
1041
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n', () => _commitDeltaState(_dl, _tf)); }
|
|
1038
1042
|
} catch (err) {
|
|
1039
1043
|
delete requestEntry.inProgress;
|
|
1040
1044
|
delete requestEntry.requestId;
|
|
1041
|
-
|
|
1042
|
-
|
|
1045
|
+
{ const _dl = _deltaOriginalMessagesLength, _tf = _deltaOriginalTailFp;
|
|
1046
|
+
_writeQueue.append(JSON.stringify(requestEntry) + '\n---\n', () => _commitDeltaState(_dl, _tf)); }
|
|
1043
1047
|
}
|
|
1044
1048
|
}
|
|
1045
1049
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// orchestration (dedup, access control, queue, inject, chunk, turn-end reply) lives in
|
|
3
3
|
// server/lib/im-bridge-core.js; this module only knows DingTalk's Stream client, ACK protocol,
|
|
4
4
|
// inbound payload shape, access-token fetch, and proactive App API send endpoints.
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
5
6
|
import { registerAdapter } from '../im-bridge-core.js';
|
|
6
7
|
import { t } from '../../i18n.js';
|
|
7
8
|
|
|
@@ -13,6 +14,8 @@ export function __setClientFactory(fn) { clientFactory = fn; }
|
|
|
13
14
|
const TOKEN_URL = 'https://api.dingtalk.com/v1.0/oauth2/accessToken';
|
|
14
15
|
const GROUP_SEND_URL = 'https://api.dingtalk.com/v1.0/robot/groupMessages/send';
|
|
15
16
|
const OTO_SEND_URL = 'https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend';
|
|
17
|
+
const CARD_CREATE_URL = 'https://api.dingtalk.com/v1.0/card/instances';
|
|
18
|
+
const CARD_DELIVER_URL = 'https://api.dingtalk.com/v1.0/card/instances/deliver';
|
|
16
19
|
|
|
17
20
|
async function getAccessToken(cfg, ctx) {
|
|
18
21
|
const tc = ctx.store.tokenCache;
|
|
@@ -120,6 +123,65 @@ const dingtalkAdapter = {
|
|
|
120
123
|
// 触发钉钉风控/限流,反过来阻断模型回复的下发。故 DingTalk 发送者头像在「对话记录」里降级为默认头像
|
|
121
124
|
// (名字仍是真实昵称,不报错、不抢占发送配额)。后续如引入具备通讯录 scope 的凭证再补 resolveSender。
|
|
122
125
|
|
|
126
|
+
async sendAckCard(cfg, target, statusText, ctx) {
|
|
127
|
+
if (!cfg.cardTemplateId) return null;
|
|
128
|
+
const token = await getAccessToken(cfg, ctx);
|
|
129
|
+
const outTrackId = randomUUID();
|
|
130
|
+
const headers = { 'Content-Type': 'application/json', 'x-acs-dingtalk-access-token': token };
|
|
131
|
+
|
|
132
|
+
const cr = await ctx.fetch(CARD_CREATE_URL, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers,
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
outTrackId,
|
|
137
|
+
cardTemplateId: cfg.cardTemplateId,
|
|
138
|
+
cardData: { cardParamMap: { status: statusText, content: '' } },
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
if (!cr.ok) { const j = await cr.json().catch(() => ({})); throw new Error(`card create ${cr.status}: ${j.message || j.code || 'failed'}`); }
|
|
142
|
+
|
|
143
|
+
const isGroup = String(target.conversationType) === '2';
|
|
144
|
+
const deliverBody = isGroup
|
|
145
|
+
? {
|
|
146
|
+
outTrackId,
|
|
147
|
+
userIdType: 1,
|
|
148
|
+
openSpaceId: 'dtv1.card//IM_GROUP.' + target.conversationId,
|
|
149
|
+
imGroupOpenDeliverModel: { robotCode: target.robotCode },
|
|
150
|
+
}
|
|
151
|
+
: {
|
|
152
|
+
outTrackId,
|
|
153
|
+
userIdType: 1,
|
|
154
|
+
openSpaceId: 'dtv1.card//IM_ROBOT.' + target.senderStaffId,
|
|
155
|
+
imRobotOpenDeliverModel: { spaceType: 'IM_ROBOT', robotCode: target.robotCode },
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const dr = await ctx.fetch(CARD_DELIVER_URL, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers,
|
|
161
|
+
body: JSON.stringify(deliverBody),
|
|
162
|
+
});
|
|
163
|
+
if (!dr.ok) { const j = await dr.json().catch(() => ({})); throw new Error(`card deliver ${dr.status}: ${j.message || j.code || 'failed'}`); }
|
|
164
|
+
|
|
165
|
+
return { outTrackId };
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
async updateAckCard(cfg, target, handle, content, status, ctx) {
|
|
169
|
+
try {
|
|
170
|
+
const token = await getAccessToken(cfg, ctx);
|
|
171
|
+
const r = await ctx.fetch(CARD_CREATE_URL, {
|
|
172
|
+
method: 'PUT',
|
|
173
|
+
headers: { 'Content-Type': 'application/json', 'x-acs-dingtalk-access-token': token },
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
outTrackId: handle.outTrackId,
|
|
176
|
+
cardData: { cardParamMap: { status: '', content } },
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
return r.ok;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
123
185
|
async testConnection(cfg, ctx) {
|
|
124
186
|
try {
|
|
125
187
|
ctx.store.tokenCache = null; // force a fresh fetch
|
|
@@ -129,6 +129,41 @@ const discordAdapter = {
|
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
131
|
|
|
132
|
+
async sendAckCard(cfg, target, statusText, ctx) {
|
|
133
|
+
const client = ctx.store.client;
|
|
134
|
+
if (!client) throw new Error('discord client not connected');
|
|
135
|
+
let channel;
|
|
136
|
+
if (target.userId) {
|
|
137
|
+
const user = await client.users.fetch(target.userId);
|
|
138
|
+
channel = await user.createDM();
|
|
139
|
+
} else {
|
|
140
|
+
channel = await client.channels.fetch(target.channelId);
|
|
141
|
+
}
|
|
142
|
+
if (!channel || typeof channel.send !== 'function') throw new Error(`channel not sendable: ${target.channelId}`);
|
|
143
|
+
const msg = await channel.send(statusText);
|
|
144
|
+
return { channelId: target.channelId, messageId: msg.id, userId: target.userId };
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
async updateAckCard(cfg, target, handle, content, status, ctx) {
|
|
148
|
+
try {
|
|
149
|
+
const client = ctx.store.client;
|
|
150
|
+
if (!client) return false;
|
|
151
|
+
let channel;
|
|
152
|
+
if (handle.userId) {
|
|
153
|
+
const user = await client.users.fetch(handle.userId);
|
|
154
|
+
channel = await user.createDM();
|
|
155
|
+
} else {
|
|
156
|
+
channel = await client.channels.fetch(handle.channelId);
|
|
157
|
+
}
|
|
158
|
+
if (!channel || typeof channel.messages?.fetch !== 'function') return false;
|
|
159
|
+
const msg = await channel.messages.fetch(handle.messageId);
|
|
160
|
+
await msg.edit(content.slice(0, DISCORD_MAX));
|
|
161
|
+
return true;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
132
167
|
async testConnection(cfg, ctx) {
|
|
133
168
|
// Validate the token via REST without opening a gateway (and without the privileged intent).
|
|
134
169
|
try {
|