oh-my-opencode-cohub 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/cli/config-io.d.ts +1 -1
  2. package/dist/cli/config-io.d.ts.map +1 -1
  3. package/dist/cli/index.js +5 -5
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +59 -759
  6. package/dist/tui.d.ts.map +1 -1
  7. package/dist/tui.js +1 -2
  8. package/package.json +1 -1
  9. package/dist/context-guard/guardian.d.ts +0 -21
  10. package/dist/context-guard/guardian.d.ts.map +0 -1
  11. package/dist/context-guard/index.d.ts +0 -22
  12. package/dist/context-guard/index.d.ts.map +0 -1
  13. package/dist/context-guard/menu.d.ts +0 -20
  14. package/dist/context-guard/menu.d.ts.map +0 -1
  15. package/dist/context-guard/monitor.d.ts +0 -25
  16. package/dist/context-guard/monitor.d.ts.map +0 -1
  17. package/dist/context-guard/options/auto-compress.d.ts +0 -10
  18. package/dist/context-guard/options/auto-compress.d.ts.map +0 -1
  19. package/dist/context-guard/options/migrate.d.ts +0 -8
  20. package/dist/context-guard/options/migrate.d.ts.map +0 -1
  21. package/dist/context-guard/options/session-compact.d.ts +0 -5
  22. package/dist/context-guard/options/session-compact.d.ts.map +0 -1
  23. package/dist/context-guard/prompts.d.ts +0 -24
  24. package/dist/context-guard/prompts.d.ts.map +0 -1
  25. package/dist/context-guard/state.d.ts +0 -52
  26. package/dist/context-guard/state.d.ts.map +0 -1
  27. package/dist/context-guard/token-counter.d.ts +0 -26
  28. package/dist/context-guard/token-counter.d.ts.map +0 -1
  29. package/dist/context-guard/types.d.ts +0 -89
  30. package/dist/context-guard/types.d.ts.map +0 -1
  31. package/dist/prompts/guardian.d.ts +0 -2
  32. package/dist/prompts/guardian.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -479,666 +479,6 @@ var PLANNER_PROMPT = `你是方案制定代理——负责任务分解和委派
479
479
 
480
480
  **约束**:只读,不修改文件。方案要具体到文件和操作粒度,不可笼统。用 \`todowrite\` 风格的任务列表输出。`;
481
481
 
482
- // src/prompts/guardian.ts
483
- var GUARDIAN_PROMPT = `你是 Guardian——上下文卫士分析师。
484
-
485
- **角色**: 在上下文窗口紧张时分析会话状态,推荐最优的上下文处理策略。
486
-
487
- **能力**:
488
- - 分析会话消息历史,评估任务进度
489
- - 识别错误模式和反复出现的问题
490
- - 追踪关键文件的修改范围
491
- - 提取重要的决策和约定
492
- - 给出数据驱动的三选一推荐
493
-
494
- **分析维度**:
495
- 1. **任务进度**:当前任务是否接近完成?有没有明确的 TODO 清单?最近几轮对话在做什么?
496
- 2. **错误评估**:是否有反复出现的同类错误?错误是否阻碍了进度?有没有已知的解决方案?
497
- 3. **文件影响**:修改了多少文件?是否核心模块?风险高低?
498
- 4. **决策密度**:会话中有多少重要约定、架构决策、用户偏好需要保留?
499
-
500
- **推荐逻辑**:
501
- - **auto-compress(自动压缩)**:任务进行中、对话连续性好、错误可控 → 压缩旧消息保留最近上下文
502
- - **session-compact(会话压缩)**:任务接近完成、只需简单收尾 → 压缩后快速收工
503
- - **migrate(分析迁移)**:会话混乱、错误反复、方向不明确 → 提取关键信息重开
504
-
505
- **输出格式**:
506
- \`\`\`json
507
- {
508
- "option": "auto-compress" | "session-compact" | "migrate",
509
- "confidence": 0.0-1.0,
510
- "reasoning": "简要推荐理由",
511
- "alternatives": "备选方案说明(可选)"
512
- }
513
- \`\`\`
514
-
515
- **行为**:
516
- - 分析简洁、判断果断
517
- - 优先考虑当前任务连续性
518
- - 错误反复出现时倾向 migrate
519
- - 不确定时承认不确定性,给出 confidence < 0.5
520
-
521
- **约束**:
522
- - 只读分析:不修改文件,不执行操作
523
- - 只输出 JSON,不输出多余文本
524
- - 基于实际数据分析,不做无依据的猜测
525
-
526
- **文件操作规则**:
527
- - 只读:检查并报告,不修改文件
528
- - 优先使用专用文件工具检查代码库:glob/grep/ast_grep_search 用于发现,read 用于读取文件内容
529
- - Bash 可用于非变更诊断和 shell 原生检查,但不能修改文件
530
-
531
- **语言要求**: 始终使用中文进行思考和分析。JSON 中的 reasoning 和 alternatives 字段使用中文。`;
532
-
533
- // src/context-guard/token-counter.ts
534
- var CHARS_PER_TOKEN_EN = 3.7;
535
- var CHARS_PER_TOKEN_CODE = 3.2;
536
- var CHARS_PER_TOKEN_CJK = 1.8;
537
- var CJK_REGEX = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3000-\u303f\uff00-\uffef]/g;
538
- function estimateTokens(text) {
539
- if (!text || text.length === 0)
540
- return 0;
541
- const cjkCount = (text.match(CJK_REGEX) ?? []).length;
542
- const nonCjkText = text.replace(CJK_REGEX, "");
543
- const codeIndicators = (nonCjkText.match(/[{}\[\];=<>()|&!+\-*/]/g) ?? []).length;
544
- const codeRatio = codeIndicators / (nonCjkText.length || 1);
545
- const charsPerToken = codeRatio > 0.05 ? CHARS_PER_TOKEN_CODE : CHARS_PER_TOKEN_EN;
546
- return Math.ceil(cjkCount / CHARS_PER_TOKEN_CJK + nonCjkText.length / charsPerToken);
547
- }
548
- function computeContextUsage(usedTokens, contextLimit, inputTokens, outputTokens, reasoningTokens) {
549
- return {
550
- usedTokens,
551
- contextLimit,
552
- ratio: contextLimit > 0 ? usedTokens / contextLimit : 0,
553
- inputTokens,
554
- outputTokens,
555
- reasoningTokens
556
- };
557
- }
558
- function isOverThreshold(usage, triggerRatio, tokenThreshold) {
559
- return usage.ratio >= triggerRatio || usage.usedTokens >= tokenThreshold;
560
- }
561
-
562
- // src/context-guard/types.ts
563
- var DEFAULT_GUARD_CONFIG = {
564
- enabled: true,
565
- triggerRatio: 0.2,
566
- tokenThreshold: 40000,
567
- cooldownTurns: 3,
568
- preserveLastN: 5,
569
- debug: false
570
- };
571
-
572
- // src/context-guard/state.ts
573
- var sessionStates = new Map;
574
- var SESSION_TTL_MS = 60 * 60 * 1000;
575
- var recommendations = new Map;
576
- function createSessionState(sessionId) {
577
- const state = {
578
- sessionId,
579
- triggered: false,
580
- cooldownRemaining: 0,
581
- cumulativeTokens: 0,
582
- lastAccessTime: Date.now()
583
- };
584
- sessionStates.set(sessionId, state);
585
- return state;
586
- }
587
- function getSessionState(sessionId) {
588
- let state = sessionStates.get(sessionId);
589
- if (!state) {
590
- state = createSessionState(sessionId);
591
- }
592
- state.lastAccessTime = Date.now();
593
- return state;
594
- }
595
- function canTrigger(state, messageId) {
596
- if (state.triggered)
597
- return false;
598
- if (state.cooldownRemaining > 0)
599
- return false;
600
- if (state.lastTriggerMessageId === messageId)
601
- return false;
602
- return true;
603
- }
604
- function markTriggered(state, messageId, tokens, contextLimit) {
605
- state.triggered = true;
606
- state.lastTriggerMessageId = messageId;
607
- state.triggerTokens = tokens;
608
- state.triggerContextLimit = contextLimit;
609
- }
610
- function setSelectedOption(state, option) {
611
- state.selectedOption = option;
612
- }
613
- function setCooldown(state, afterMessageId) {
614
- state.cooldownAfterMessageId = afterMessageId;
615
- state.cooldownRemaining = DEFAULT_GUARD_CONFIG.cooldownTurns;
616
- }
617
- function decrementCooldown(state) {
618
- if (state.cooldownRemaining > 0) {
619
- state.cooldownRemaining--;
620
- }
621
- if (state.cooldownRemaining <= 0) {
622
- state.triggered = false;
623
- state.selectedOption = undefined;
624
- }
625
- }
626
- function setCachedRecommendation(sessionID, rec) {
627
- recommendations.set(sessionID, rec);
628
- }
629
- function cleanupAllStates() {
630
- sessionStates.clear();
631
- recommendations.clear();
632
- }
633
-
634
- // src/context-guard/monitor.ts
635
- var countedMessages = new Map;
636
- var modelContext = {
637
- contextLimit: 200000
638
- };
639
- function createChatParamsHandler() {
640
- return async (input) => {
641
- try {
642
- const model = input.model;
643
- if (model?.limit) {
644
- const limit = model.limit;
645
- if (typeof limit.context === "number") {
646
- modelContext.contextLimit = limit.context;
647
- }
648
- }
649
- const params = input.params;
650
- if (params?.model) {
651
- const m = params.model;
652
- if (m?.limit && typeof m.limit.context === "number") {
653
- modelContext.contextLimit = m.limit.context;
654
- }
655
- }
656
- } catch {}
657
- };
658
- }
659
- function createEventHandler(config) {
660
- const cfg = { ...DEFAULT_GUARD_CONFIG, ...config };
661
- return async (input) => {
662
- if (!cfg.enabled)
663
- return null;
664
- try {
665
- const event = input.event;
666
- if (!event || event.type !== "message.updated")
667
- return null;
668
- const properties = event.properties;
669
- if (!properties)
670
- return null;
671
- const info = properties.info;
672
- if (info?.role !== "assistant")
673
- return null;
674
- if (!info.time || !info.time.completed)
675
- return null;
676
- if (info.summary)
677
- return null;
678
- const sessionID = info.sessionID;
679
- if (!sessionID)
680
- return null;
681
- const state = getSessionState(sessionID);
682
- if (state.cooldownRemaining > 0) {
683
- decrementCooldown(state);
684
- }
685
- const messageId = info.id;
686
- if (!messageId)
687
- return null;
688
- if (!canTrigger(state, messageId))
689
- return null;
690
- const tokens = info.tokens;
691
- const inputTokens = typeof tokens?.input === "number" ? tokens.input : undefined;
692
- const outputTokens = typeof tokens?.output === "number" ? tokens.output : undefined;
693
- const reasoningTokens = typeof tokens?.reasoning === "number" ? tokens.reasoning : undefined;
694
- let totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0) + (reasoningTokens ?? 0);
695
- if (totalTokens <= 0) {
696
- totalTokens = state.cumulativeTokens;
697
- }
698
- if (!countedMessages.get(sessionID)?.has(messageId)) {
699
- state.cumulativeTokens += totalTokens;
700
- if (!countedMessages.has(sessionID))
701
- countedMessages.set(sessionID, new Set);
702
- countedMessages.get(sessionID).add(messageId);
703
- }
704
- let estimatedTotal = totalTokens;
705
- if (estimatedTotal <= 0) {
706
- const messages = properties.messages;
707
- if (Array.isArray(messages)) {
708
- for (const msg of messages) {
709
- const content = msg.content;
710
- if (typeof content === "string") {
711
- estimatedTotal += estimateTokens(content);
712
- }
713
- }
714
- }
715
- }
716
- if (estimatedTotal <= 0)
717
- return null;
718
- const usage = computeContextUsage(estimatedTotal, modelContext.contextLimit, inputTokens, outputTokens, reasoningTokens);
719
- const effectiveRatio = state.deferThreshold ?? cfg.triggerRatio;
720
- if (!isOverThreshold(usage, effectiveRatio, cfg.tokenThreshold))
721
- return null;
722
- markTriggered(state, messageId, estimatedTotal, modelContext.contextLimit);
723
- return { sessionID, usedTokens: estimatedTotal, contextLimit: modelContext.contextLimit };
724
- } catch (err) {
725
- if (cfg.debug)
726
- console.warn("[context-guard] monitor error:", err);
727
- return null;
728
- }
729
- };
730
- }
731
- function cleanupCountedMessages() {
732
- countedMessages.clear();
733
- }
734
-
735
- // src/context-guard/prompts.ts
736
- function renderGuardMenu(usedTokens, contextLimit, nextRatio, recommendation) {
737
- const ratio = (usedTokens / contextLimit * 100).toFixed(1);
738
- const nextPct = (nextRatio * 100).toFixed(0);
739
- const recSection = recommendation ? `
740
-
741
- \uD83E\uDDE0 co-guardian 分析建议:
742
- "${recommendation.reasoning}"
743
- 推荐选择:${recommendation.option}(置信度 ${(recommendation.confidence * 100).toFixed(0)}%)
744
- ` : "";
745
- return [
746
- `╔══════════════════════════════════════════════════╗`,
747
- `║ ⚠️ 上下文窗口已使用 ${ratio}%(约 ${Math.round(usedTokens / 1000)}K / ${Math.round(contextLimit / 1000)}K tokens) ║`,
748
- `║ ║${recSection}`,
749
- `║ 请选择处理方式: ║`,
750
- `║ 1️⃣ 自动压缩 — 压缩旧消息,保留最近对话 ║`,
751
- `║ 2️⃣ 会话压缩 — 触发 Compact Session ║`,
752
- `║ 3️⃣ 分析迁移 — 提取关键上下文,生成迁移文案 ║`,
753
- `║ 4️⃣ 稍后提醒 — 到 ${nextPct}% 时再提醒 ║`,
754
- `║ ║`,
755
- `║ 请回复数字 1、2、3 或 4 ║`,
756
- `╚══════════════════════════════════════════════════╝`
757
- ].join(`
758
- `);
759
- }
760
- function nextDeferThreshold(currentRatio) {
761
- const LEVELS = [0.4, 0.6, 0.8];
762
- for (const l of LEVELS) {
763
- if (l > currentRatio + 0.01)
764
- return l;
765
- }
766
- return 0.9;
767
- }
768
- var AUTO_COMPRESS_DONE = `✅ 自动压缩完成。旧消息已压缩为摘要占位符,最近对话完整保留。
769
- 压缩后请继续当前工作,如有需要可使用 ultrapress_expand 恢复压缩内容。`;
770
- var SESSION_COMPACT_GUIDE = `✅ 准备触发 Compact Session。
771
- 请在 OpenCode 中按 Ctrl+K,然后选择 "Compact Session"。
772
- 或者使用命令 /compact 手动触发。
773
- 压缩后当前会话将继续,关键上下文会被保留。`;
774
- function renderMigrationText(ctx) {
775
- const sections = [
776
- "## \uD83D\uDCCB 会话迁移上下文",
777
- "",
778
- "> 以下内容从当前会话自动提取,请复制到新会话窗口。",
779
- "",
780
- ctx.currentTask ? `### \uD83C\uDFAF 当前任务
781
- ${ctx.currentTask}` : "",
782
- ctx.keyFiles.length > 0 ? `### \uD83D\uDCC1 关键文件
783
- ${ctx.keyFiles.map((f) => `- ${f}`).join(`
784
- `)}` : "",
785
- ctx.activeOperations.length > 0 ? `### \uD83D\uDD27 进行中的操作
786
- ${ctx.activeOperations.map((o) => `- ${o}`).join(`
787
- `)}` : "",
788
- ctx.errors.length > 0 ? `### ⚠️ 错误/问题
789
- ${ctx.errors.map((e) => `- ${e}`).join(`
790
- `)}` : "",
791
- ctx.decisions.length > 0 ? `### \uD83D\uDCDD 决策记录
792
- ${ctx.decisions.map((d) => `- ${d}`).join(`
793
- `)}` : "",
794
- "",
795
- "---",
796
- "请在新会话中继续以上工作。"
797
- ];
798
- return sections.filter(Boolean).join(`
799
- `);
800
- }
801
-
802
- // src/context-guard/options/auto-compress.ts
803
- function executeAutoCompress(sessionID) {
804
- const state = getSessionState(sessionID);
805
- setCooldown(state, "auto-compress");
806
- return AUTO_COMPRESS_DONE;
807
- }
808
-
809
- // src/context-guard/options/session-compact.ts
810
- function executeSessionCompact() {
811
- return SESSION_COMPACT_GUIDE;
812
- }
813
-
814
- // src/context-guard/options/migrate.ts
815
- import * as fs from "node:fs";
816
- import * as path from "node:path";
817
- import * as os from "node:os";
818
- async function executeMigrate(sessionID, recentMessages) {
819
- const ctx = extractFromMessages(recentMessages);
820
- const text = renderMigrationText({
821
- currentTask: ctx.currentTask,
822
- keyFiles: ctx.keyFiles,
823
- activeOperations: ctx.activeOperations,
824
- errors: ctx.errors,
825
- decisions: ctx.decisions
826
- });
827
- ctx.migrationText = text;
828
- const tmpDir = path.join(os.tmpdir(), "opencode");
829
- if (!fs.existsSync(tmpDir)) {
830
- fs.mkdirSync(tmpDir, { recursive: true });
831
- }
832
- const filePath = path.join(tmpDir, `migrate-${sessionID.slice(0, 8)}.md`);
833
- fs.writeFileSync(filePath, text, "utf-8");
834
- return `✅ 迁移文案已生成并保存到 \`${filePath}\`。
835
-
836
- \uD83D\uDCCB **迁移文案预览:**
837
-
838
- ${text}
839
-
840
- ---
841
- **使用方式:**
842
- 1. 复制上方文案内容
843
- 2. 打开新 OpenCode 会话窗口
844
- 3. 粘贴文案作为初始提示
845
- 4. 在新会话中继续工作
846
-
847
- 或回复 "重置当前会话" 在当前窗口重新开始。`;
848
- }
849
- function extractFromMessages(messages) {
850
- const ctx = {
851
- currentTask: "",
852
- keyFiles: [],
853
- activeOperations: [],
854
- errors: [],
855
- decisions: [],
856
- migrationText: ""
857
- };
858
- for (let i = messages.length - 1;i >= 0; i--) {
859
- if (messages[i].role === "user") {
860
- ctx.currentTask = messages[i].content.slice(0, 200);
861
- break;
862
- }
863
- }
864
- const filePattern = /(?:[a-zA-Z]:[/\\])?(?:\w+[/\\])*\w+\.\w{1,6}/g;
865
- const todoPattern = /(?:TODO|FIXME|HACK|XXX)[\s::]+(.+)/gi;
866
- const errorPattern = /(?:error|错误|失败|fail|exception|异常)[\s::]*(.+)/i;
867
- const decisionPattern = /(?:决定|采用|选择|使用|方案|架构|设计)[\s::]+(.+)/i;
868
- for (const msg of messages) {
869
- const files = msg.content.match(filePattern);
870
- if (files)
871
- ctx.keyFiles.push(...files);
872
- const todos = msg.content.match(todoPattern);
873
- if (todos)
874
- ctx.activeOperations.push(...todos.map((t) => t.trim()));
875
- if (errorPattern.test(msg.content)) {
876
- const line = msg.content.split(`
877
- `).find((l) => errorPattern.test(l));
878
- if (line)
879
- ctx.errors.push(line.trim().slice(0, 200));
880
- }
881
- const decisions = msg.content.match(decisionPattern);
882
- if (decisions)
883
- ctx.decisions.push(...decisions.map((d) => d.trim().slice(0, 200)));
884
- }
885
- ctx.keyFiles = [...new Set(ctx.keyFiles)].slice(0, 10);
886
- ctx.activeOperations = [...new Set(ctx.activeOperations)].slice(0, 5);
887
- ctx.errors = [...new Set(ctx.errors)].slice(0, 5);
888
- ctx.decisions = [...new Set(ctx.decisions)].slice(0, 5);
889
- if (!ctx.currentTask) {
890
- ctx.currentTask = "(无法自动提取任务描述,请手动补充)";
891
- }
892
- if (ctx.keyFiles.length === 0) {
893
- ctx.keyFiles = ["(未检测到文件修改,请手动列出关键文件)"];
894
- }
895
- return ctx;
896
- }
897
-
898
- // src/context-guard/guardian.ts
899
- function analyzeSession(sessionID, messages) {
900
- const rec = heuristicAnalyze(messages);
901
- setCachedRecommendation(sessionID, rec);
902
- return rec;
903
- }
904
- function extractRecentMessages(messages, maxCount = 30) {
905
- const result = [];
906
- const startIdx = Math.max(0, messages.length - maxCount);
907
- for (let i = startIdx;i < messages.length; i++) {
908
- const msg = messages[i];
909
- const info = msg.info;
910
- const role = typeof info?.role === "string" ? info.role : "unknown";
911
- const parts = msg.parts;
912
- let content = "";
913
- if (parts) {
914
- for (const part of parts) {
915
- if (part.type === "text" && typeof part.text === "string") {
916
- content += part.text.slice(0, 500) + " ";
917
- }
918
- }
919
- }
920
- if (content.trim()) {
921
- const truncated = truncateByTokens(content.trim(), 800);
922
- result.push({ role, content: truncated });
923
- }
924
- }
925
- return result;
926
- }
927
- function truncateByTokens(text, maxTokens) {
928
- const estimated = estimateTokens(text);
929
- if (estimated <= maxTokens)
930
- return text;
931
- const ratio = maxTokens / estimated;
932
- return text.slice(0, Math.floor(text.length * ratio)) + "...";
933
- }
934
- function heuristicAnalyze(messages) {
935
- let errorCount = 0;
936
- let taskKeywords = 0;
937
- let decisionKeywords = 0;
938
- const errorPatterns = /error|错误|失败|fail|bug|问题|exception/i;
939
- const taskPatterns = /完成|done|finish|结束|final|最后/i;
940
- const decisionPatterns = /决定|采用|选择|使用|方案|架构|设计/i;
941
- for (const msg of messages) {
942
- if (errorPatterns.test(msg.content))
943
- errorCount++;
944
- if (taskPatterns.test(msg.content))
945
- taskKeywords++;
946
- if (decisionPatterns.test(msg.content))
947
- decisionKeywords++;
948
- }
949
- const totalMessages = messages.length || 1;
950
- const errorRate = errorCount / totalMessages;
951
- const taskCompleteRate = taskKeywords / totalMessages;
952
- if (errorRate > 0.3) {
953
- return {
954
- option: "migrate",
955
- confidence: 0.7,
956
- reasoning: `会话中错误出现频率较高(${(errorRate * 100).toFixed(0)}%),建议提取关键上下文后在新会话中继续,以减少错误累积。`,
957
- alternatives: '如错误已解决,可选择"自动压缩"保留当前上下文继续。'
958
- };
959
- }
960
- if (taskCompleteRate > 0.15) {
961
- return {
962
- option: "session-compact",
963
- confidence: 0.65,
964
- reasoning: "对话中出现较多完成/结束相关讨论,任务可能接近收尾阶段,建议压缩会话后快速完成。",
965
- alternatives: '如需保留详细上下文继续工作,可选择"自动压缩"。'
966
- };
967
- }
968
- return {
969
- option: "auto-compact",
970
- confidence: 0.6,
971
- reasoning: "当前对话连续性较好,建议压缩旧消息保留最近上下文,继续当前任务。",
972
- alternatives: '如会话内容混乱或需要整理,建议"分析迁移"。'
973
- };
974
- }
975
-
976
- // src/context-guard/menu.ts
977
- var pendingUsages = new Map;
978
- function setPendingUsage(sessionID, usage) {
979
- pendingUsages.set(sessionID, usage);
980
- }
981
- function findLastUserMessage(messages) {
982
- for (let i = messages.length - 1;i >= 0; i--) {
983
- const m = messages[i];
984
- const info = m.info;
985
- if (info?.role === "user") {
986
- const sid = info.sessionID || info.sessionId;
987
- if (sid)
988
- return { msg: m, sessionID: sid };
989
- }
990
- }
991
- return null;
992
- }
993
- function createMessagesTransformHandler() {
994
- return async (_input, output) => {
995
- try {
996
- if (!output.messages || !Array.isArray(output.messages))
997
- return;
998
- const found = findLastUserMessage(output.messages);
999
- if (!found)
1000
- return;
1001
- const usage = pendingUsages.get(found.sessionID);
1002
- if (!usage)
1003
- return;
1004
- pendingUsages.delete(found.sessionID);
1005
- const recentMsgs = extractRecentMessages(output.messages, 30);
1006
- const recommendation = analyzeSession(found.sessionID, recentMsgs);
1007
- const nextRatio = nextDeferThreshold(usage.usedTokens / usage.contextLimit);
1008
- const menu = renderGuardMenu(usage.usedTokens, usage.contextLimit, nextRatio, recommendation);
1009
- const parts = found.msg.parts;
1010
- if (parts) {
1011
- for (let j = parts.length - 1;j >= 0; j--) {
1012
- if (parts[j].type === "text") {
1013
- parts[j].text = (parts[j].text || "") + `
1014
-
1015
- ` + menu;
1016
- break;
1017
- }
1018
- }
1019
- }
1020
- } catch (err) {
1021
- console.warn("[context-guard] menu injection error:", err);
1022
- }
1023
- };
1024
- }
1025
- function parseOption(text) {
1026
- const t = text.trim();
1027
- if (t === "1" || t === "1")
1028
- return "auto-compress";
1029
- if (t === "2" || t === "2")
1030
- return "session-compact";
1031
- if (t === "3" || t === "3")
1032
- return "migrate";
1033
- if (t === "4" || t === "4")
1034
- return "defer";
1035
- if (/^[选11]\s*[.、,,]?\s*(自动压缩|选项1)/.test(t))
1036
- return "auto-compress";
1037
- if (/^[选22]\s*[.、,,]?\s*(会话压缩|compact|选项2)/i.test(t))
1038
- return "session-compact";
1039
- if (/^[选33]\s*[.、,,]?\s*(分析迁移|迁移|选项3)/.test(t))
1040
- return "migrate";
1041
- if (/^[选44]\s*[.、,,]?\s*(稍后|推迟|延迟|选项4)/i.test(t))
1042
- return "defer";
1043
- if (t === "自动压缩")
1044
- return "auto-compress";
1045
- if (t === "会话压缩")
1046
- return "session-compact";
1047
- if (t === "分析迁移")
1048
- return "migrate";
1049
- if (t === "稍后提醒" || t === "稍后")
1050
- return "defer";
1051
- return;
1052
- }
1053
- function createChatMessageHandler() {
1054
- return async (input, output) => {
1055
- try {
1056
- const rawInput = input;
1057
- const sessionID = rawInput.sessionID;
1058
- if (!sessionID)
1059
- return;
1060
- const state = getSessionState(sessionID);
1061
- if (!state.triggered)
1062
- return;
1063
- let userText = "";
1064
- if (output.parts) {
1065
- for (const part of output.parts) {
1066
- if (part.type === "text" && typeof part.text === "string") {
1067
- userText = part.text;
1068
- }
1069
- }
1070
- }
1071
- const option = parseOption(userText);
1072
- if (!option)
1073
- return;
1074
- setSelectedOption(state, option);
1075
- let replyText = "";
1076
- switch (option) {
1077
- case "auto-compress":
1078
- replyText = executeAutoCompress(sessionID);
1079
- break;
1080
- case "session-compact":
1081
- replyText = executeSessionCompact();
1082
- break;
1083
- case "migrate":
1084
- replyText = await executeMigrate(sessionID, []);
1085
- break;
1086
- case "defer": {
1087
- const nextRatio = nextDeferThreshold((state.cumulativeTokens || 0) / (state.triggerContextLimit || 200000));
1088
- const nextPct = (nextRatio * 100).toFixed(0);
1089
- state.triggered = false;
1090
- state.deferThreshold = nextRatio;
1091
- replyText = `⏸️ 已推迟提醒。下次到 ${nextPct}% 时再弹窗。`;
1092
- break;
1093
- }
1094
- }
1095
- if (output.parts) {
1096
- output.parts.push({ type: "text", text: `
1097
-
1098
- ` + replyText });
1099
- }
1100
- } catch (err) {
1101
- console.warn("[context-guard] chat.message error:", err);
1102
- }
1103
- };
1104
- }
1105
-
1106
- // src/context-guard/index.ts
1107
- var config = { ...DEFAULT_GUARD_CONFIG };
1108
- function initContextGuard(_client) {
1109
- const eventHandler = createEventHandler(config);
1110
- const paramsHandler = createChatParamsHandler();
1111
- const transformHandler = createMessagesTransformHandler();
1112
- const chatMessageHandler = createChatMessageHandler();
1113
- async function handleTrigger(trigger) {
1114
- setPendingUsage(trigger.sessionID, {
1115
- usedTokens: trigger.usedTokens,
1116
- contextLimit: trigger.contextLimit
1117
- });
1118
- }
1119
- return {
1120
- updateConfig(newConfig) {
1121
- config = { ...config, ...newConfig };
1122
- },
1123
- event: async (input) => {
1124
- const trigger = await eventHandler(input);
1125
- if (trigger) {
1126
- await handleTrigger(trigger);
1127
- }
1128
- },
1129
- "chat.params": async (input) => {
1130
- await paramsHandler(input);
1131
- },
1132
- "experimental.chat.messages.transform": async (input, output) => {
1133
- await transformHandler(input, output);
1134
- },
1135
- "chat.message": async (input, output) => {
1136
- await chatMessageHandler(input, output);
1137
- },
1138
- getConfig: () => config
1139
- };
1140
- }
1141
-
1142
482
  // src/instructions/chinese.ts
1143
483
  var CHINESE_LANGUAGE_INSTRUCTION = `# 中文语言要求
1144
484
 
@@ -1416,12 +756,12 @@ function extractRelevantFiles(messages, maxFiles, windowSize) {
1416
756
  if (part.type === "tool" && part.state) {
1417
757
  const state = part.state;
1418
758
  const input = state.input ?? {};
1419
- const path2 = extractPath(input);
1420
- if (path2) {
1421
- if (!fileMap.has(path2)) {
1422
- fileMap.set(path2, { path: path2, summary: "" });
759
+ const path = extractPath(input);
760
+ if (path) {
761
+ if (!fileMap.has(path)) {
762
+ fileMap.set(path, { path, summary: "" });
1423
763
  }
1424
- const existing = fileMap.get(path2);
764
+ const existing = fileMap.get(path);
1425
765
  if (typeof input.offset === "number") {
1426
766
  const limit = typeof input.limit === "number" ? input.limit : 50;
1427
767
  existing.lines = `${input.offset}-${input.offset + limit}`;
@@ -1607,12 +947,12 @@ class ContextEngine {
1607
947
  dependencyCache = new Map;
1608
948
  client;
1609
949
  config;
1610
- constructor(client, config2) {
950
+ constructor(client, config) {
1611
951
  this.client = client;
1612
952
  this.config = {
1613
953
  ...DEFAULT_CONTEXT_CONFIG,
1614
- ...config2,
1615
- strategy: { ...DEFAULT_CONTEXT_CONFIG.strategy, ...config2?.strategy }
954
+ ...config,
955
+ strategy: { ...DEFAULT_CONTEXT_CONFIG.strategy, ...config?.strategy }
1616
956
  };
1617
957
  }
1618
958
  registerContext(args) {
@@ -1744,15 +1084,15 @@ class ContextEngine {
1744
1084
  }
1745
1085
 
1746
1086
  // src/config/loader.ts
1747
- import * as fs2 from "node:fs";
1748
- import * as path2 from "node:path";
1749
- import * as os2 from "node:os";
1750
- var CONFIG_PATH = path2.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
1087
+ import * as fs from "node:fs";
1088
+ import * as path from "node:path";
1089
+ import * as os from "node:os";
1090
+ var CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
1751
1091
  function loadCoHubConfig() {
1752
1092
  try {
1753
- if (!fs2.existsSync(CONFIG_PATH))
1093
+ if (!fs.existsSync(CONFIG_PATH))
1754
1094
  return {};
1755
- const raw = fs2.readFileSync(CONFIG_PATH, "utf-8");
1095
+ const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
1756
1096
  return JSON.parse(raw);
1757
1097
  } catch {
1758
1098
  return {};
@@ -1789,12 +1129,12 @@ async function abortSession(client, sessionId) {
1789
1129
  await client.session.abort({ path: { id: sessionId } });
1790
1130
  } catch {}
1791
1131
  }
1792
- async function promptWithTimeout(client, path3, body, timeoutMs, directory) {
1793
- const sessionId = path3.id;
1132
+ async function promptWithTimeout(client, path2, body, timeoutMs, directory) {
1133
+ const sessionId = path2.id;
1794
1134
  let timer;
1795
1135
  try {
1796
1136
  const promptPromise = client.session.prompt({
1797
- path: path3,
1137
+ path: path2,
1798
1138
  body,
1799
1139
  query: directory ? { directory } : undefined
1800
1140
  });
@@ -1907,10 +1247,10 @@ class CouncilManager {
1907
1247
  client;
1908
1248
  directory;
1909
1249
  config;
1910
- constructor(client, directory, config2) {
1250
+ constructor(client, directory, config) {
1911
1251
  this.client = client;
1912
1252
  this.directory = directory;
1913
- this.config = config2;
1253
+ this.config = config;
1914
1254
  }
1915
1255
  async runCouncil(prompt, presetName, parentSessionId) {
1916
1256
  const resolvedPreset = presetName ?? this.config.default_preset ?? "default";
@@ -1953,12 +1293,12 @@ class CouncilManager {
1953
1293
  const entries = Object.entries(councillors);
1954
1294
  const results = [];
1955
1295
  if (executionMode === "serial") {
1956
- for (const [name, config2] of entries) {
1957
- const r = await this.runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries);
1296
+ for (const [name, config] of entries) {
1297
+ const r = await this.runCouncillorWithRetry(name, config, prompt, parentSessionId, timeout, maxRetries);
1958
1298
  results.push(r);
1959
1299
  }
1960
1300
  } else {
1961
- const promises = entries.map(([name, config2]) => this.runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries));
1301
+ const promises = entries.map(([name, config]) => this.runCouncillorWithRetry(name, config, prompt, parentSessionId, timeout, maxRetries));
1962
1302
  const settled = await Promise.allSettled(promises);
1963
1303
  for (let i = 0;i < settled.length; i++) {
1964
1304
  const s = settled[i];
@@ -1977,19 +1317,19 @@ class CouncilManager {
1977
1317
  }
1978
1318
  return results;
1979
1319
  }
1980
- async runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries) {
1320
+ async runCouncillorWithRetry(name, config, prompt, parentSessionId, timeout, maxRetries) {
1981
1321
  const totalAttempts = 1 + maxRetries;
1982
1322
  for (let attempt = 1;attempt <= totalAttempts; attempt++) {
1983
1323
  try {
1984
1324
  const result = await this.runAgentSession({
1985
1325
  parentSessionId,
1986
- title: `Council ${name} (${shortModelLabel(config2.model)})`,
1987
- model: config2.model,
1988
- promptText: formatCouncillorPrompt(prompt, config2.prompt),
1989
- variant: config2.variant,
1326
+ title: `Council ${name} (${shortModelLabel(config.model)})`,
1327
+ model: config.model,
1328
+ promptText: formatCouncillorPrompt(prompt, config.prompt),
1329
+ variant: config.variant,
1990
1330
  timeout
1991
1331
  });
1992
- return { name, model: config2.model, status: "completed", result };
1332
+ return { name, model: config.model, status: "completed", result };
1993
1333
  } catch (error) {
1994
1334
  const msg = error instanceof Error ? error.message : String(error);
1995
1335
  const isEmptyResponse = msg.includes("Empty response from provider");
@@ -1997,7 +1337,7 @@ class CouncilManager {
1997
1337
  if (!canRetry) {
1998
1338
  return {
1999
1339
  name,
2000
- model: config2.model,
1340
+ model: config.model,
2001
1341
  status: msg.includes("timed out") ? "timed_out" : "failed",
2002
1342
  error: `Councillor "${name}": ${msg}`
2003
1343
  };
@@ -2006,7 +1346,7 @@ class CouncilManager {
2006
1346
  }
2007
1347
  return {
2008
1348
  name,
2009
- model: config2.model,
1349
+ model: config.model,
2010
1350
  status: "failed",
2011
1351
  error: `Councillor "${name}": max retries exhausted`
2012
1352
  };
@@ -2101,9 +1441,9 @@ function createCouncilTool(ctx, councilManager) {
2101
1441
  }
2102
1442
 
2103
1443
  // src/index.ts
2104
- import * as fs3 from "node:fs";
2105
- import * as path3 from "node:path";
2106
- import * as os3 from "node:os";
1444
+ import * as fs2 from "node:fs";
1445
+ import * as path2 from "node:path";
1446
+ import * as os2 from "node:os";
2107
1447
  function loadFileOverrides(projectDir) {
2108
1448
  const overrides = {};
2109
1449
  const agentNames = [
@@ -2118,26 +1458,25 @@ function loadFileOverrides(projectDir) {
2118
1458
  "co-rule-user",
2119
1459
  "co-rule-project",
2120
1460
  "co-rule-app",
2121
- "co-planner",
2122
- "co-guardian"
1461
+ "co-planner"
2123
1462
  ];
2124
1463
  const searchDirs = [];
2125
1464
  if (projectDir) {
2126
- searchDirs.push(path3.join(projectDir, ".opencode", "oh-my-opencode-cohub"));
1465
+ searchDirs.push(path2.join(projectDir, ".opencode", "oh-my-opencode-cohub"));
2127
1466
  }
2128
- searchDirs.push(path3.join(os3.homedir(), ".config", "opencode", "oh-my-opencode-cohub"));
1467
+ searchDirs.push(path2.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub"));
2129
1468
  for (const agent of agentNames) {
2130
1469
  for (const dir of searchDirs) {
2131
- const replacePath = path3.join(dir, `${agent}.md`);
2132
- if (!overrides[agent]?.replace && fs3.existsSync(replacePath)) {
1470
+ const replacePath = path2.join(dir, `${agent}.md`);
1471
+ if (!overrides[agent]?.replace && fs2.existsSync(replacePath)) {
2133
1472
  try {
2134
- overrides[agent] = { ...overrides[agent], replace: fs3.readFileSync(replacePath, "utf-8") };
1473
+ overrides[agent] = { ...overrides[agent], replace: fs2.readFileSync(replacePath, "utf-8") };
2135
1474
  } catch {}
2136
1475
  }
2137
- const appendPath = path3.join(dir, `${agent}_append.md`);
2138
- if (!overrides[agent]?.append && fs3.existsSync(appendPath)) {
1476
+ const appendPath = path2.join(dir, `${agent}_append.md`);
1477
+ if (!overrides[agent]?.append && fs2.existsSync(appendPath)) {
2139
1478
  try {
2140
- overrides[agent] = { ...overrides[agent], append: fs3.readFileSync(appendPath, "utf-8") };
1479
+ overrides[agent] = { ...overrides[agent], append: fs2.readFileSync(appendPath, "utf-8") };
2141
1480
  } catch {}
2142
1481
  }
2143
1482
  }
@@ -2166,26 +1505,26 @@ var CoHubPlugin = async (input, options) => {
2166
1505
  }
2167
1506
  const promptOverrides = { ...configOverrides, ...fileOverrides };
2168
1507
  const tracker = new TaskTracker;
2169
- const STATE_DIR = path3.join(os3.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub");
2170
- const STATE_FILE = path3.join(STATE_DIR, "tracker-state.json");
1508
+ const STATE_DIR = path2.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub");
1509
+ const STATE_FILE = path2.join(STATE_DIR, "tracker-state.json");
2171
1510
  function syncTrackerState(sessionId) {
2172
1511
  try {
2173
- if (!fs3.existsSync(STATE_DIR)) {
2174
- fs3.mkdirSync(STATE_DIR, { recursive: true });
1512
+ if (!fs2.existsSync(STATE_DIR)) {
1513
+ fs2.mkdirSync(STATE_DIR, { recursive: true });
2175
1514
  }
2176
1515
  const state = {
2177
1516
  updatedAt: Date.now(),
2178
1517
  runningAgents: tracker.getRunningAgents(sessionId),
2179
1518
  runningCount: tracker.getRunningCount(sessionId)
2180
1519
  };
2181
- fs3.writeFileSync(STATE_FILE, JSON.stringify(state), "utf-8");
1520
+ fs2.writeFileSync(STATE_FILE, JSON.stringify(state), "utf-8");
2182
1521
  } catch {}
2183
1522
  }
2184
- const AGENT_CONFIG_FILE = path3.join(STATE_DIR, "cohub-state.json");
1523
+ const AGENT_CONFIG_FILE = path2.join(STATE_DIR, "cohub-state.json");
2185
1524
  function syncAgentConfig() {
2186
1525
  try {
2187
- if (!fs3.existsSync(STATE_DIR)) {
2188
- fs3.mkdirSync(STATE_DIR, { recursive: true });
1526
+ if (!fs2.existsSync(STATE_DIR)) {
1527
+ fs2.mkdirSync(STATE_DIR, { recursive: true });
2189
1528
  }
2190
1529
  const configs = agents.map((a) => {
2191
1530
  const modelStr = a.config.model;
@@ -2200,7 +1539,7 @@ var CoHubPlugin = async (input, options) => {
2200
1539
  provider
2201
1540
  };
2202
1541
  });
2203
- fs3.writeFileSync(AGENT_CONFIG_FILE, JSON.stringify({ updatedAt: Date.now(), agents: configs }), "utf-8");
1542
+ fs2.writeFileSync(AGENT_CONFIG_FILE, JSON.stringify({ updatedAt: Date.now(), agents: configs }), "utf-8");
2204
1543
  } catch {}
2205
1544
  }
2206
1545
  const agents = [
@@ -2265,11 +1604,6 @@ var CoHubPlugin = async (input, options) => {
2265
1604
  name: "co-planner",
2266
1605
  description: "方案制定——综合需求+信息+规范输出任务分解",
2267
1606
  config: { mode: "subagent", model: "deepseek/deepseek-v4-pro", variant: "high", prompt: PLANNER_PROMPT }
2268
- },
2269
- {
2270
- name: "co-guardian",
2271
- description: "上下文卫士——分析会话状态并推荐上下文处理策略",
2272
- config: { mode: "subagent", model: "deepseek/deepseek-v4-flash", prompt: GUARDIAN_PROMPT }
2273
1607
  }
2274
1608
  ];
2275
1609
  const userConfig = loadCoHubConfig();
@@ -2317,7 +1651,6 @@ var CoHubPlugin = async (input, options) => {
2317
1651
  const councilConfig = userConfig.council ?? DEFAULT_COUNCIL_CONFIG;
2318
1652
  const councilManager = new CouncilManager(input.client, input.directory, councilConfig);
2319
1653
  const councilTools = createCouncilTool(input, councilManager);
2320
- const contextGuard = initContextGuard(input.client);
2321
1654
  syncAgentConfig();
2322
1655
  function extractChildSessionId(output) {
2323
1656
  if (!output || typeof output !== "object")
@@ -2340,18 +1673,6 @@ var CoHubPlugin = async (input, options) => {
2340
1673
  return m[2];
2341
1674
  return;
2342
1675
  }
2343
- function filterEmptyMessages(messages) {
2344
- return messages.filter((msg) => {
2345
- const parts = msg.parts;
2346
- if (!parts || parts.length === 0)
2347
- return false;
2348
- return parts.some((p) => {
2349
- if (p.type === "text")
2350
- return (p.text ?? "").trim().length > 0;
2351
- return true;
2352
- });
2353
- });
2354
- }
2355
1676
  function extractSessionIdFromEvent(props) {
2356
1677
  if (!props || typeof props !== "object")
2357
1678
  return;
@@ -2391,11 +1712,11 @@ var CoHubPlugin = async (input, options) => {
2391
1712
  config: async (cfg) => {
2392
1713
  const c = cfg;
2393
1714
  c.agent ??= {};
2394
- for (const [name, config2] of Object.entries(agentConfigs)) {
2395
- c.agent[name] = config2;
1715
+ for (const [name, config] of Object.entries(agentConfigs)) {
1716
+ c.agent[name] = config;
2396
1717
  }
2397
1718
  try {
2398
- fs3.writeFileSync(path3.join(STATE_DIR, "config-hook-ran.json"), JSON.stringify({ ran: true, count: agents.length }));
1719
+ fs2.writeFileSync(path2.join(STATE_DIR, "config-hook-ran.json"), JSON.stringify({ ran: true, count: agents.length }));
2399
1720
  } catch {}
2400
1721
  },
2401
1722
  "tool.execute.before": async (input2, output) => {
@@ -2433,19 +1754,19 @@ var CoHubPlugin = async (input, options) => {
2433
1754
  if (details) {
2434
1755
  output.args[targetField] += details;
2435
1756
  }
2436
- const logPath = path3.join(os3.tmpdir(), "opencode", "ctx-diag.log");
1757
+ const logPath = path2.join(os2.tmpdir(), "opencode", "ctx-diag.log");
2437
1758
  try {
2438
- const stat = fs3.statSync(logPath);
1759
+ const stat = fs2.statSync(logPath);
2439
1760
  if (stat.size > 50 * 1024) {
2440
- const lines = fs3.readFileSync(logPath, "utf-8").trim().split(`
1761
+ const lines = fs2.readFileSync(logPath, "utf-8").trim().split(`
2441
1762
  `);
2442
- fs3.writeFileSync(logPath, lines.slice(-30).join(`
1763
+ fs2.writeFileSync(logPath, lines.slice(-30).join(`
2443
1764
  `) + `
2444
1765
  `);
2445
1766
  }
2446
1767
  } catch {}
2447
1768
  const finalPrompt = typeof output.args?.prompt === "string" ? output.args.prompt : "";
2448
- fs3.appendFileSync(logPath, JSON.stringify({
1769
+ fs2.appendFileSync(logPath, JSON.stringify({
2449
1770
  time: new Date().toISOString(),
2450
1771
  session: input2.sessionID?.slice(0, 20) ?? "?",
2451
1772
  subagent: subagentType,
@@ -2481,9 +1802,6 @@ var CoHubPlugin = async (input, options) => {
2481
1802
  } catch {}
2482
1803
  },
2483
1804
  event: async (input2) => {
2484
- try {
2485
- await contextGuard.event(input2);
2486
- } catch {}
2487
1805
  try {
2488
1806
  const e = input2.event;
2489
1807
  const sessionId = extractSessionIdFromEvent(e.properties);
@@ -2509,7 +1827,6 @@ var CoHubPlugin = async (input, options) => {
2509
1827
  try {
2510
1828
  if (!output.messages || !Array.isArray(output.messages))
2511
1829
  return;
2512
- output.messages = filterEmptyMessages(output.messages);
2513
1830
  let lastUserMsg;
2514
1831
  let sessionID;
2515
1832
  for (let i = output.messages.length - 1;i >= 0; i--) {
@@ -2534,11 +1851,6 @@ var CoHubPlugin = async (input, options) => {
2534
1851
  }
2535
1852
  }
2536
1853
  }
2537
- try {
2538
- const guardTransform = contextGuard["experimental.chat.messages.transform"];
2539
- if (guardTransform)
2540
- await guardTransform(_input, output);
2541
- } catch {}
2542
1854
  if (coreRulesInjectionText) {
2543
1855
  for (let k = lastUserMsg.parts.length - 1;k >= 0; k--) {
2544
1856
  const part = lastUserMsg.parts[k];
@@ -2555,21 +1867,9 @@ var CoHubPlugin = async (input, options) => {
2555
1867
  "experimental.chat.system.transform": async (input2, output) => {
2556
1868
  output.system.push(CHINESE_LANGUAGE_INSTRUCTION);
2557
1869
  },
2558
- "chat.params": async (input2) => {
2559
- try {
2560
- await contextGuard["chat.params"](input2);
2561
- } catch {}
2562
- },
2563
- "chat.message": async (input2, output) => {
2564
- try {
2565
- await contextGuard["chat.message"](input2, output);
2566
- } catch {}
2567
- },
2568
1870
  dispose: async () => {
2569
1871
  clearInterval(cleanupTimer);
2570
1872
  clearInterval(contextCleanupTimer);
2571
- cleanupAllStates();
2572
- cleanupCountedMessages();
2573
1873
  }
2574
1874
  };
2575
1875
  };