foliko 1.1.65 → 1.1.66

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.
@@ -579,6 +579,435 @@
579
579
  "text": "<think>用户要求获取今日新闻。我已经从Google News RSS获取了中文新闻。让我整理一下主要新闻内容给用户。\n</think>\n\n## 📰 今日新闻速报 (2026年5月23日 星期六)\n\n---\n\n### 🔥 头条要闻\n| 新闻 | 来源 |\n|------|------|\n| **习近平对山西煤矿瓦斯爆炸事故作出重要指示** | 新华社 |\n\n### 🛠️ 国内重点\n\n| 类别 | 新闻 |\n|------|------|\n| **安全生产** | 山西长治市沁源县煤矿瓦斯爆炸,已致90人死亡,习近平作出重要指示 |\n| **航天事业** | 神二十三乘组确定:朱杨柱、张志远、**黎家盈(首位香港航天员)** |\n| **国际合作** | 巴基斯坦航天员入列中国训练中心,将执行短期飞行任务 |\n| **两岸交流** | 第28届海峡两岸经贸交易会在福州开幕 |\n| **防汛** | 国家防总针对湖北、重庆启动防汛四级应急响应 |\n| **金融监管** | 中国证监会拟对富途罚款18.5亿、老虎证券罚没4.1亿 |\n| **反腐** | 青海省供销合作社联合社原主任朱小青被查 |\n\n### 🚀 科技财经\n- **SpaceX 成功发射 Starship V3** - 人类史上最大最强火箭,NASA局长点赞\n- 道指涨近300点创新高,芯片股爆发,高通狂飙12%\n\n### 🎬 娱乐文化\n- 电影《给阿嬷的情书》票房破8亿\n- 《歌手2026》窦靖童演唱,那英从头哭到尾\n- 电视剧《主角》带火秦腔热潮,53亿播放量\n\n### 📊 体育要闻\n- **\"东北超\"足球联赛**今日揭幕,哈尔滨队主场迎战大连队\n- 法网:中国女网选手郑钦文等8人出战正赛\n\n### 🌍 国际新闻\n- 美伊谈判进展:沙特媒体称美伊协议草案或数小时内公布\n- 世卫上调刚果(金)埃博拉疫情风险至\"非常高\"\n- 印尼苏门答腊岛大规模停电,数百万人受影响"
580
580
  }
581
581
  ]
582
+ },
583
+ {
584
+ "role": "user",
585
+ "content": "你好"
586
+ },
587
+ {
588
+ "role": "assistant",
589
+ "content": [
590
+ {
591
+ "type": "text",
592
+ "text": "<think>用户简单打招呼,保持友好回应即可。\n</think>\n\n你好!👋 有什么需要帮忙的吗?"
593
+ }
594
+ ]
595
+ },
596
+ {
597
+ "role": "user",
598
+ "content": "你好"
599
+ },
600
+ {
601
+ "role": "assistant",
602
+ "content": [
603
+ {
604
+ "type": "text",
605
+ "text": "<think>用户又说\"你好\",这是一种简单的问候。我会友好地回应并询问需要什么帮助。\n</think>\n\n你好!😄 有什么我可以帮你的吗?"
606
+ }
607
+ ]
608
+ },
609
+ {
610
+ "role": "user",
611
+ "content": "你好"
612
+ },
613
+ {
614
+ "role": "assistant",
615
+ "content": [
616
+ {
617
+ "type": "text",
618
+ "text": "<think>用户又发了一次\"你好\"。我应该继续友好地回应,并询问是否需要帮助。\n</think>\n\n你好!👋\n\n需要什么帮助吗?我可以帮你:\n\n- 📰 查看新闻\n- 💻 编写或修改代码\n- 📝 创建文件\n- 🔍 搜索内容\n- ⚙️ 执行系统任务\n\n请告诉我你需要什么!"
619
+ }
620
+ ]
621
+ },
622
+ {
623
+ "role": "user",
624
+ "content": "开启微信插件"
625
+ },
626
+ {
627
+ "role": "assistant",
628
+ "content": [
629
+ {
630
+ "type": "text",
631
+ "text": "<think>用户想要开启微信插件。让我先检查一下当前有哪些插件可用,然后看看是否有微信相关的插件。\n</think>\n\n\n"
632
+ },
633
+ {
634
+ "type": "tool-call",
635
+ "toolCallId": "call_function_hrvoxi5a0dmd_1",
636
+ "toolName": "list_plugins",
637
+ "input": {}
638
+ }
639
+ ]
640
+ },
641
+ {
642
+ "role": "tool",
643
+ "content": [
644
+ {
645
+ "type": "tool-result",
646
+ "toolCallId": "call_function_hrvoxi5a0dmd_1",
647
+ "toolName": "list_plugins",
648
+ "output": {
649
+ "type": "json",
650
+ "value": {
651
+ "success": true,
652
+ "data": [
653
+ {
654
+ "name": "defaults",
655
+ "status": "loaded",
656
+ "enabled": true,
657
+ "version": "1.0.0",
658
+ "system": true
659
+ },
660
+ {
661
+ "name": "install",
662
+ "status": "loaded",
663
+ "enabled": true,
664
+ "version": "1.0.0",
665
+ "system": true
666
+ },
667
+ {
668
+ "name": "ai",
669
+ "status": "loaded",
670
+ "enabled": true,
671
+ "version": "1.0.0",
672
+ "system": true
673
+ },
674
+ {
675
+ "name": "skill-manager",
676
+ "status": "loaded",
677
+ "enabled": true,
678
+ "version": "1.0.0",
679
+ "system": true
680
+ },
681
+ {
682
+ "name": "mcp",
683
+ "status": "loaded",
684
+ "enabled": true,
685
+ "version": "1.0.0",
686
+ "system": true
687
+ },
688
+ {
689
+ "name": "python-plugin-loader",
690
+ "status": "loaded",
691
+ "enabled": true,
692
+ "version": "1.0.0",
693
+ "system": true
694
+ },
695
+ {
696
+ "name": "subagent-manager",
697
+ "status": "loaded",
698
+ "enabled": true,
699
+ "version": "1.0.0",
700
+ "system": false
701
+ },
702
+ {
703
+ "name": "data-splitter",
704
+ "status": "loaded",
705
+ "enabled": true,
706
+ "version": "1.0.0",
707
+ "system": false
708
+ },
709
+ {
710
+ "name": "poster",
711
+ "status": "loaded",
712
+ "enabled": true,
713
+ "version": "1.2.2",
714
+ "system": false
715
+ },
716
+ {
717
+ "name": "puppeteer",
718
+ "status": "registered",
719
+ "enabled": false,
720
+ "version": "1.0.0",
721
+ "system": false
722
+ },
723
+ {
724
+ "name": "ambient",
725
+ "status": "loaded",
726
+ "enabled": true,
727
+ "version": "1.0.0",
728
+ "system": true
729
+ },
730
+ {
731
+ "name": "audit",
732
+ "status": "loaded",
733
+ "enabled": true,
734
+ "version": "1.0.0",
735
+ "system": true
736
+ },
737
+ {
738
+ "name": "coordinator",
739
+ "status": "loaded",
740
+ "enabled": true,
741
+ "version": "1.0.0",
742
+ "system": false
743
+ },
744
+ {
745
+ "name": "email",
746
+ "status": "loaded",
747
+ "enabled": true,
748
+ "version": "1.1.0",
749
+ "system": false
750
+ },
751
+ {
752
+ "name": "extension-executor",
753
+ "status": "loaded",
754
+ "enabled": true,
755
+ "version": "1.0.0",
756
+ "system": true
757
+ },
758
+ {
759
+ "name": "feishu",
760
+ "status": "registered",
761
+ "enabled": false,
762
+ "version": "1.1.0",
763
+ "system": false
764
+ },
765
+ {
766
+ "name": "file-system",
767
+ "status": "loaded",
768
+ "enabled": true,
769
+ "version": "1.0.0",
770
+ "system": true
771
+ },
772
+ {
773
+ "name": "gate-trading",
774
+ "status": "loaded",
775
+ "enabled": true,
776
+ "version": "1.1.0",
777
+ "system": false
778
+ },
779
+ {
780
+ "name": "memory",
781
+ "status": "loaded",
782
+ "enabled": true,
783
+ "version": "1.0.0",
784
+ "system": true
785
+ },
786
+ {
787
+ "name": "plugin_manager",
788
+ "status": "loaded",
789
+ "enabled": true,
790
+ "version": "1.0.0",
791
+ "system": true
792
+ },
793
+ {
794
+ "name": "python-executor",
795
+ "status": "loaded",
796
+ "enabled": true,
797
+ "version": "1.0.0",
798
+ "system": true
799
+ },
800
+ {
801
+ "name": "qq",
802
+ "status": "registered",
803
+ "enabled": false,
804
+ "version": "1.0.0",
805
+ "system": false
806
+ },
807
+ {
808
+ "name": "rules",
809
+ "status": "loaded",
810
+ "enabled": true,
811
+ "version": "1.0.0",
812
+ "system": true
813
+ },
814
+ {
815
+ "name": "scheduler",
816
+ "status": "loaded",
817
+ "enabled": true,
818
+ "version": "1.0.0",
819
+ "system": true
820
+ },
821
+ {
822
+ "name": "session",
823
+ "status": "loaded",
824
+ "enabled": true,
825
+ "version": "1.0.0",
826
+ "system": true
827
+ },
828
+ {
829
+ "name": "shell-executor",
830
+ "status": "loaded",
831
+ "enabled": true,
832
+ "version": "1.0.0",
833
+ "system": true
834
+ },
835
+ {
836
+ "name": "storage",
837
+ "status": "loaded",
838
+ "enabled": true,
839
+ "version": "1.0.0",
840
+ "system": true
841
+ },
842
+ {
843
+ "name": "telegram",
844
+ "status": "registered",
845
+ "enabled": false,
846
+ "version": "2.1.0",
847
+ "system": false
848
+ },
849
+ {
850
+ "name": "think",
851
+ "status": "loaded",
852
+ "enabled": true,
853
+ "version": "1.0.0",
854
+ "system": false
855
+ },
856
+ {
857
+ "name": "tools",
858
+ "status": "loaded",
859
+ "enabled": true,
860
+ "version": "1.0.0",
861
+ "system": true
862
+ },
863
+ {
864
+ "name": "web",
865
+ "status": "loaded",
866
+ "enabled": true,
867
+ "version": "3.1.0",
868
+ "system": true
869
+ },
870
+ {
871
+ "name": "weixin",
872
+ "status": "registered",
873
+ "enabled": false,
874
+ "version": "1.0.0",
875
+ "system": false
876
+ }
877
+ ]
878
+ }
879
+ }
880
+ }
881
+ ]
882
+ },
883
+ {
884
+ "role": "assistant",
885
+ "content": [
886
+ {
887
+ "type": "text",
888
+ "text": "<think>用户想要开启微信插件。当前状态是 \"weixin\" 插件的状态是 \"registered\"(已注册)但 enabled=false(未启用)。\n\n我需要使用 enable_plugin 工具来启用它。\n</think>\n\n\n"
889
+ },
890
+ {
891
+ "type": "tool-call",
892
+ "toolCallId": "call_function_sdgilnl2d1at_1",
893
+ "toolName": "enable_plugin",
894
+ "input": {
895
+ "name": "weixin"
896
+ }
897
+ }
898
+ ]
899
+ },
900
+ {
901
+ "role": "tool",
902
+ "content": [
903
+ {
904
+ "type": "tool-result",
905
+ "toolCallId": "call_function_sdgilnl2d1at_1",
906
+ "toolName": "enable_plugin",
907
+ "output": {
908
+ "type": "json",
909
+ "value": {
910
+ "success": true,
911
+ "data": "插件 'weixin' 已启用"
912
+ }
913
+ }
914
+ }
915
+ ]
916
+ },
917
+ {
918
+ "role": "assistant",
919
+ "content": [
920
+ {
921
+ "type": "text",
922
+ "text": "<think>WeChat plugin is now enabled. Let me check its tools and configuration.\n</think>\n\n\n"
923
+ },
924
+ {
925
+ "type": "tool-call",
926
+ "toolCallId": "call_function_1x2enmbwvu5q_1",
927
+ "toolName": "get_plugin_config",
928
+ "input": {
929
+ "name": "weixin"
930
+ }
931
+ }
932
+ ]
933
+ },
934
+ {
935
+ "role": "tool",
936
+ "content": [
937
+ {
938
+ "type": "tool-result",
939
+ "toolCallId": "call_function_1x2enmbwvu5q_1",
940
+ "toolName": "get_plugin_config",
941
+ "output": {
942
+ "type": "json",
943
+ "value": {
944
+ "success": true,
945
+ "data": {
946
+ "forceLogin": false,
947
+ "qrcodeTerminal": true,
948
+ "allowedUsers": [],
949
+ "systemPrompt": "你是一个有帮助的AI助手。回复内容不要使用markdown格式文本"
950
+ }
951
+ }
952
+ }
953
+ }
954
+ ]
955
+ },
956
+ {
957
+ "role": "assistant",
958
+ "content": [
959
+ {
960
+ "type": "text",
961
+ "text": "<think>用户说\"开启微信插件\",我已经帮他开启了。但是现在这个插件需要更详细的配置才能工作,比如需要微信账号、API配置等。让我给用户一个提示。\n</think>\n\n✅ **微信插件已开启!**\n\n当前配置:\n| 配置项 | 值 |\n|--------|-----|\n| QR码终端显示 | ✅ 已开启 |\n| 强制登录 | ❌ 关闭 |\n| 允许用户 | 未设置(所有人可访问) |\n\n---\n\n⚠️ **注意**:微信插件需要额外配置才能使用:\n\n1. **微信公众号** - 需要 AppID 和 AppSecret\n2. **企业微信Webhook** - 需要 Webhook URL\n3. **WeChat Work** - 需要企业配置\n\n需要我帮你进一步配置吗?或者你是想使用其他社交插件(如 Telegram)?"
962
+ }
963
+ ]
964
+ },
965
+ {
966
+ "role": "user",
967
+ "content": "关闭微信插件"
968
+ },
969
+ {
970
+ "role": "assistant",
971
+ "content": [
972
+ {
973
+ "type": "text",
974
+ "text": "<think>用户想要关闭微信插件。\n</think>\n\n\n"
975
+ },
976
+ {
977
+ "type": "tool-call",
978
+ "toolCallId": "call_function_cei29xgckbhn_1",
979
+ "toolName": "disable_plugin",
980
+ "input": {
981
+ "name": "weixin"
982
+ }
983
+ }
984
+ ]
985
+ },
986
+ {
987
+ "role": "tool",
988
+ "content": [
989
+ {
990
+ "type": "tool-result",
991
+ "toolCallId": "call_function_cei29xgckbhn_1",
992
+ "toolName": "disable_plugin",
993
+ "output": {
994
+ "type": "json",
995
+ "value": {
996
+ "success": true,
997
+ "data": "插件 'weixin' 已禁用"
998
+ }
999
+ }
1000
+ }
1001
+ ]
1002
+ },
1003
+ {
1004
+ "role": "assistant",
1005
+ "content": [
1006
+ {
1007
+ "type": "text",
1008
+ "text": "<think>微信插件已成功关闭。\n</think>\n\n✅ **微信插件已关闭!**"
1009
+ }
1010
+ ]
582
1011
  }
583
1012
  ],
584
1013
  "variables": {},
@@ -0,0 +1,25 @@
1
+ {
2
+ "sessionId": "weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat",
3
+ "messages": [
4
+ {
5
+ "role": "user",
6
+ "content": "你好"
7
+ },
8
+ {
9
+ "role": "assistant",
10
+ "content": [
11
+ {
12
+ "type": "text",
13
+ "text": "<think>用户只是打了个招呼\"你好\",这是一个简单的问候。我应该友好地回应,并简短地介绍自己或询问有什么可以帮助的。</think>\n\n你好!我是智能助手,可以帮助你完成各种任务。\n\n**我擅长的领域:**\n\n- 📊 **数据分析与可视化** - 数据处理、统计分析、报表生成\n- 🎨 **海报设计** - 复古杂志风、记录类海报制作\n- 💻 **开发支持** - 前端/后端开发、代码重构、API设计\n- 🔒 **安全审计** - 代码安全、漏洞检测\n- ⏰ **定时任务** - 定时提醒、周期监控\n- 📧 **邮件管理** - 收发邮件、监控新邮件\n- 💹 **加密货币** - Gate.io 交易与行情监控\n- 🤖 **自动化** - Ambient Agent 持续监控任务\n\n有什么我可以帮你的吗?"
14
+ }
15
+ ]
16
+ }
17
+ ],
18
+ "variables": {},
19
+ "metadata": {
20
+ "createdAt": 1779527430311,
21
+ "lastActive": 1779527430311,
22
+ "messageCount": 0,
23
+ "compressionCount": 0
24
+ }
25
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 检查所有 require 路径是否有效
3
+ * 支持 plugins/, src/, cli/src/
4
+ */
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const dirs = [
9
+ path.resolve(__dirname, 'plugins'),
10
+ path.resolve(__dirname, 'src'),
11
+ path.resolve(__dirname, 'cli/src'),
12
+ ];
13
+
14
+ let totalErrors = 0;
15
+
16
+ function checkFile(filePath, baseDir) {
17
+ let content;
18
+ try {
19
+ content = fs.readFileSync(filePath, 'utf8');
20
+ } catch(e) {
21
+ return false;
22
+ }
23
+
24
+ const requireRe = /require\s*\(\s*[']([^']+)[']\)/g;
25
+ const dir = path.dirname(filePath);
26
+ let match;
27
+ let found = false;
28
+
29
+ while ((match = requireRe.exec(content)) !== null) {
30
+ const reqPath = match[1];
31
+ // Only check relative paths
32
+ if (!reqPath.startsWith('./') && !reqPath.startsWith('../')) continue;
33
+
34
+ const resolved = path.resolve(dir, reqPath);
35
+ const candidates = [resolved, resolved + '.js', path.join(resolved, 'index.js')];
36
+ const exists = candidates.some(c => {
37
+ try { return fs.existsSync(c); } catch(e) { return false; }
38
+ });
39
+ if (!exists) {
40
+ const rel = path.relative(baseDir || __dirname, filePath);
41
+ console.log('✗ ' + rel + ' → ' + reqPath);
42
+ found = true;
43
+ totalErrors++;
44
+ }
45
+ }
46
+ return found;
47
+ }
48
+
49
+ function scanDir(dir, baseDir) {
50
+ let hasError = false;
51
+ let entries;
52
+ try {
53
+ entries = fs.readdirSync(dir, { withFileTypes: true });
54
+ } catch(e) {
55
+ return false;
56
+ }
57
+ for (const entry of entries) {
58
+ if (entry.name === 'node_modules') continue;
59
+ const fullPath = path.join(dir, entry.name);
60
+ if (entry.isDirectory()) {
61
+ if (scanDir(fullPath, baseDir)) hasError = true;
62
+ } else if (entry.name.endsWith('.js')) {
63
+ if (checkFile(fullPath, baseDir)) hasError = true;
64
+ }
65
+ }
66
+ return hasError;
67
+ }
68
+
69
+ console.log('检查 require 路径...\n');
70
+ for (const dir of dirs) {
71
+ const rel = path.relative(__dirname, dir);
72
+ console.log('── ' + rel + '/ ──');
73
+ scanDir(dir, dir);
74
+ console.log('');
75
+ }
76
+
77
+ if (totalErrors === 0) {
78
+ console.log('✓ 所有 require 路径都有效');
79
+ } else {
80
+ console.log(`共 ${totalErrors} 个无效路径`);
81
+ }
82
+ console.log('\n检查完成');
@@ -78,6 +78,8 @@ function parseArgs(args) {
78
78
  options.quiet = true;
79
79
  } else if (arg === '--no-quiet') {
80
80
  options.quiet = false;
81
+ } else if (arg === '--old') {
82
+ options.oldUI = true;
81
83
  }
82
84
  }
83
85
 
@@ -150,19 +152,23 @@ async function chatCommand(args) {
150
152
 
151
153
  // 初始化框架
152
154
  const framework = new Framework({ debug: false });
153
-
154
- // 保存原始 emit 函数,用于事件发送
155
- const emitLog = (...args) => framework.emit('console:log', args);
156
- const emitWarn = (...args) => framework.emit('console:warn', args);
157
- const emitInfo = (...args) => framework.emit('console:info', args);
158
- const emitDebug = (...args) => framework.emit('console:debug', args);
159
- const emitError = (...args) => framework.emit('console:error', args);
160
-
161
- console.log = emitLog;
162
- console.warn = emitWarn;
163
- console.info = emitInfo;
164
- console.debug = emitDebug;
165
- console.error = emitError;
155
+
156
+ // 声明 emit 函数(函数级作用域,供后续使用)
157
+ let emitLog, emitWarn, emitInfo, emitDebug, emitError;
158
+ if(!options.oldUI){
159
+ // 保存原始 emit 函数,用于事件发送
160
+ emitLog = (...args) => framework.emit('console:log', args);
161
+ emitWarn = (...args) => framework.emit('console:warn', args);
162
+ emitInfo = (...args) => framework.emit('console:info', args);
163
+ emitDebug = (...args) => framework.emit('console:debug', args);
164
+ emitError = (...args) => framework.emit('console:error', args);
165
+
166
+ console.log = emitLog;
167
+ console.warn = emitWarn;
168
+ console.info = emitInfo;
169
+ console.debug = emitDebug;
170
+ console.error = emitError;
171
+ }
166
172
  //framework.logger.setSilent(true);
167
173
  await framework.bootstrap({
168
174
  agentDir: process.cwd() + '/.agent',
@@ -203,7 +209,7 @@ async function chatCommand(args) {
203
209
  - 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`);
204
210
 
205
211
  // 静默模式:抑制控制台打印,但保持事件发送(让 ChatUI 能收到日志显示到 tooler)
206
- if (options.quiet) {
212
+ if (options.quiet&&!options.oldUI) {
207
213
  // 保持事件发送,只是不打印到原始控制台
208
214
  console.log = emitLog;
209
215
  console.warn = emitWarn;
@@ -217,8 +223,13 @@ async function chatCommand(args) {
217
223
  }
218
224
 
219
225
 
220
- // 初始化 UI
221
- const ui = new ChatUI(agent, { stream: options.stream });
226
+ // 初始化 UI(--old 使用旧版 readline 界面)
227
+ let ChatUIClass = ChatUI;
228
+ if (options.oldUI) {
229
+ const { ChatUI: ChatUIOld } = require('../ui/chat-ui-old');
230
+ ChatUIClass = ChatUIOld;
231
+ }
232
+ const ui = new ChatUIClass(agent, { stream: options.stream });
222
233
 
223
234
  // 监听通知事件,在 CLI 中显示
224
235
  // 显示当前会话的通知,以及没有指定 sessionId 的广播通知
package/cli/src/index.js CHANGED
@@ -108,6 +108,7 @@ Chat Options:
108
108
  --model <name> 指定 AI 模型
109
109
  --provider <name> 指定 AI 提供商
110
110
  --base-url <url> 指定 API 基础地址
111
+ --old 使用旧版 readline 界面(不使用 TUI)
111
112
 
112
113
  Examples:
113
114
  foliko chat
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 防抖函数 - 等待 delay 时间内没有新调用才执行
3
+ * @param {Function} fn - 要防抖的函数
4
+ * @param {number} delay - 延迟毫秒
5
+ * @returns {{run: Function, cancel: Function}}
6
+ */
7
+ function debounce(fn, delay) {
8
+ let timer = null;
9
+
10
+ const debounced = (...args) => {
11
+ if (timer) {
12
+ clearTimeout(timer);
13
+ }
14
+ timer = setTimeout(() => {
15
+ fn(...args);
16
+ timer = null;
17
+ }, delay);
18
+ };
19
+
20
+ const cancel = () => {
21
+ if (timer) {
22
+ clearTimeout(timer);
23
+ timer = null;
24
+ }
25
+ };
26
+
27
+ return { run: debounced, cancel };
28
+ }
29
+
30
+ function throttle(fn, delay = 300, options = {}) {
31
+ let timer = null;
32
+ let lastTime = 0;
33
+ const { leading = true, trailing = true } = options;
34
+
35
+ return function (...args) {
36
+ const now = Date.now();
37
+
38
+ // 首次调用且不允许立即执行
39
+ if (!leading && lastTime === 0) {
40
+ lastTime = now;
41
+ }
42
+
43
+ // 距离下次执行还需要等待的时间
44
+ const remaining = delay - (now - lastTime);
45
+
46
+ if (remaining <= 0 || remaining > delay) {
47
+ // 可以立即执行
48
+ if (timer) {
49
+ clearTimeout(timer);
50
+ timer = null;
51
+ }
52
+ fn.apply(this, args);
53
+ lastTime = now;
54
+ } else if (!timer && trailing) {
55
+ // 设置定时器,确保最后一次调用能执行
56
+ timer = setTimeout(() => {
57
+ fn.apply(this, args);
58
+ lastTime = leading ? Date.now() : 0;
59
+ timer = null;
60
+ }, remaining);
61
+ }
62
+ };
63
+ }
64
+
65
+ /**
66
+ * 间隔执行函数 - 每隔 interval 毫秒执行一次,直到被取消
67
+ * @param {Function} fn - 要执行的函数
68
+ * @param {number} interval - 间隔毫秒
69
+ * @param {boolean} [lazy=false] - 如果为 true,start() 不会立即执行,只开始计时
70
+ * @returns {{run: Function, cancel: Function}}
71
+ */
72
+ function interval(fn, intervalMs, lazy = false) {
73
+ let timer = null;
74
+
75
+ const start = () => {
76
+ // 先清除已存在的定时器
77
+ if (timer) {
78
+ clearInterval(timer);
79
+ }
80
+
81
+ // 如果不是 lazy 模式,立即执行一次
82
+ if (!lazy) {
83
+ fn();
84
+ }
85
+
86
+ // 设置间隔定时器
87
+ timer = setInterval(() => {
88
+ fn();
89
+ }, intervalMs);
90
+ };
91
+
92
+ const stop = () => {
93
+ if (timer) {
94
+ clearInterval(timer);
95
+ timer = null;
96
+ }
97
+ };
98
+
99
+ return { start, stop };
100
+ }
101
+
102
+ module.exports = {
103
+ debounce,
104
+ throttle,
105
+ interval,
106
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.65",
3
+ "version": "1.1.66",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",