@zhin.js/agent 0.0.0 → 0.0.1

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 (139) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +226 -0
  3. package/lib/agent.d.ts +9 -7
  4. package/lib/agent.d.ts.map +1 -1
  5. package/lib/agent.js +63 -29
  6. package/lib/agent.js.map +1 -1
  7. package/lib/bootstrap.js +1 -1
  8. package/lib/bootstrap.js.map +1 -1
  9. package/lib/builtin-tools.d.ts.map +1 -1
  10. package/lib/builtin-tools.js +34 -31
  11. package/lib/builtin-tools.js.map +1 -1
  12. package/lib/compaction.js +1 -1
  13. package/lib/compaction.js.map +1 -1
  14. package/lib/context-manager.js +1 -1
  15. package/lib/context-manager.js.map +1 -1
  16. package/lib/conversation-memory.d.ts +11 -0
  17. package/lib/conversation-memory.d.ts.map +1 -1
  18. package/lib/conversation-memory.js +39 -1
  19. package/lib/conversation-memory.js.map +1 -1
  20. package/lib/cron-engine.d.ts.map +1 -1
  21. package/lib/cron-engine.js +5 -3
  22. package/lib/cron-engine.js.map +1 -1
  23. package/lib/follow-up.js +1 -1
  24. package/lib/follow-up.js.map +1 -1
  25. package/lib/hooks.js +1 -1
  26. package/lib/hooks.js.map +1 -1
  27. package/lib/index.d.ts +2 -0
  28. package/lib/index.d.ts.map +1 -1
  29. package/lib/index.js +1 -0
  30. package/lib/index.js.map +1 -1
  31. package/lib/init/create-zhin-agent.d.ts +3 -0
  32. package/lib/init/create-zhin-agent.d.ts.map +1 -0
  33. package/lib/init/create-zhin-agent.js +127 -0
  34. package/lib/init/create-zhin-agent.js.map +1 -0
  35. package/lib/init/register-ai-service.d.ts +7 -0
  36. package/lib/init/register-ai-service.d.ts.map +1 -0
  37. package/lib/init/register-ai-service.js +42 -0
  38. package/lib/init/register-ai-service.js.map +1 -0
  39. package/lib/init/register-ai-trigger.d.ts +7 -0
  40. package/lib/init/register-ai-trigger.d.ts.map +1 -0
  41. package/lib/init/register-ai-trigger.js +163 -0
  42. package/lib/init/register-ai-trigger.js.map +1 -0
  43. package/lib/init/register-builtin-tools.d.ts +3 -0
  44. package/lib/init/register-builtin-tools.d.ts.map +1 -0
  45. package/lib/init/register-builtin-tools.js +179 -0
  46. package/lib/init/register-builtin-tools.js.map +1 -0
  47. package/lib/init/register-db-models.d.ts +2 -0
  48. package/lib/init/register-db-models.d.ts.map +1 -0
  49. package/lib/init/register-db-models.js +28 -0
  50. package/lib/init/register-db-models.js.map +1 -0
  51. package/lib/init/register-db-upgrade.d.ts +10 -0
  52. package/lib/init/register-db-upgrade.d.ts.map +1 -0
  53. package/lib/init/register-db-upgrade.js +67 -0
  54. package/lib/init/register-db-upgrade.js.map +1 -0
  55. package/lib/init/register-management-tools.d.ts +6 -0
  56. package/lib/init/register-management-tools.d.ts.map +1 -0
  57. package/lib/init/register-management-tools.js +68 -0
  58. package/lib/init/register-management-tools.js.map +1 -0
  59. package/lib/init/register-message-recorder.d.ts +3 -0
  60. package/lib/init/register-message-recorder.d.ts.map +1 -0
  61. package/lib/init/register-message-recorder.js +27 -0
  62. package/lib/init/register-message-recorder.js.map +1 -0
  63. package/lib/init/register-tool-service.d.ts +2 -0
  64. package/lib/init/register-tool-service.d.ts.map +1 -0
  65. package/lib/init/register-tool-service.js +9 -0
  66. package/lib/init/register-tool-service.js.map +1 -0
  67. package/lib/init/shared-refs.d.ts +14 -0
  68. package/lib/init/shared-refs.d.ts.map +1 -0
  69. package/lib/init/shared-refs.js +7 -0
  70. package/lib/init/shared-refs.js.map +1 -0
  71. package/lib/init/types.d.ts +15 -0
  72. package/lib/init/types.d.ts.map +1 -0
  73. package/lib/init/types.js +7 -0
  74. package/lib/init/types.js.map +1 -0
  75. package/lib/init.d.ts +14 -17
  76. package/lib/init.d.ts.map +1 -1
  77. package/lib/init.js +34 -670
  78. package/lib/init.js.map +1 -1
  79. package/lib/rate-limiter.js +1 -1
  80. package/lib/rate-limiter.js.map +1 -1
  81. package/lib/service.js +2 -2
  82. package/lib/service.js.map +1 -1
  83. package/lib/session.d.ts +7 -0
  84. package/lib/session.d.ts.map +1 -1
  85. package/lib/session.js +26 -4
  86. package/lib/session.js.map +1 -1
  87. package/lib/storage.d.ts +68 -0
  88. package/lib/storage.d.ts.map +1 -0
  89. package/lib/storage.js +105 -0
  90. package/lib/storage.js.map +1 -0
  91. package/lib/subagent.js +1 -1
  92. package/lib/subagent.js.map +1 -1
  93. package/lib/tools.d.ts.map +1 -1
  94. package/lib/tools.js +8 -9
  95. package/lib/tools.js.map +1 -1
  96. package/lib/user-profile.js +1 -1
  97. package/lib/user-profile.js.map +1 -1
  98. package/lib/zhin-agent/builtin-tools.js.map +1 -1
  99. package/lib/zhin-agent/config.d.ts +1 -1
  100. package/lib/zhin-agent/config.d.ts.map +1 -1
  101. package/lib/zhin-agent/config.js.map +1 -1
  102. package/lib/zhin-agent/index.js +1 -1
  103. package/lib/zhin-agent/index.js.map +1 -1
  104. package/lib/zhin-agent/tool-collector.js +3 -3
  105. package/lib/zhin-agent/tool-collector.js.map +1 -1
  106. package/package.json +4 -3
  107. package/src/agent.ts +53 -30
  108. package/src/bootstrap.ts +1 -1
  109. package/src/builtin-tools.ts +44 -40
  110. package/src/compaction.ts +1 -1
  111. package/src/context-manager.ts +1 -1
  112. package/src/conversation-memory.ts +43 -1
  113. package/src/cron-engine.ts +5 -3
  114. package/src/follow-up.ts +1 -1
  115. package/src/hooks.ts +1 -1
  116. package/src/index.ts +10 -0
  117. package/src/init/create-zhin-agent.ts +133 -0
  118. package/src/init/register-ai-service.ts +53 -0
  119. package/src/init/register-ai-trigger.ts +179 -0
  120. package/src/init/register-builtin-tools.ts +177 -0
  121. package/src/init/register-db-models.ts +31 -0
  122. package/src/init/register-db-upgrade.ts +77 -0
  123. package/src/init/register-management-tools.ts +71 -0
  124. package/src/init/register-message-recorder.ts +31 -0
  125. package/src/init/register-tool-service.ts +9 -0
  126. package/src/init/shared-refs.ts +20 -0
  127. package/src/init/types.ts +18 -0
  128. package/src/init.ts +35 -735
  129. package/src/rate-limiter.ts +1 -1
  130. package/src/service.ts +4 -4
  131. package/src/session.ts +26 -4
  132. package/src/storage.ts +135 -0
  133. package/src/subagent.ts +1 -1
  134. package/src/tools.ts +7 -10
  135. package/src/user-profile.ts +1 -1
  136. package/src/zhin-agent/builtin-tools.ts +2 -2
  137. package/src/zhin-agent/config.ts +3 -3
  138. package/src/zhin-agent/index.ts +1 -1
  139. package/src/zhin-agent/tool-collector.ts +8 -8
@@ -19,7 +19,7 @@ import * as os from 'os';
19
19
  import * as path from 'path';
20
20
  import { exec } from 'child_process';
21
21
  import { promisify } from 'util';
22
- import { Logger } from '@zhin.js/logger';
22
+ import { Logger, type PropertySchema } from '@zhin.js/core';
23
23
  import { ZhinTool } from '@zhin.js/core';
24
24
 
25
25
  // 从新模块中 re-export 向后兼容的函数
@@ -28,6 +28,10 @@ export { loadSoulPersona, loadToolsGuide, loadAgentsMemory } from './bootstrap.j
28
28
  const execAsync = promisify(exec);
29
29
  const logger = new Logger(null, 'builtin-tools');
30
30
 
31
+ function errMsg(e: unknown): string {
32
+ return e instanceof Error ? e.message : String(e);
33
+ }
34
+
31
35
  /**
32
36
  * 获取数据目录路径
33
37
  */
@@ -202,8 +206,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
202
206
  return files.length === 0
203
207
  ? `No files matching '${args.pattern}'`
204
208
  : `Found ${files.length} files:\n${files.join('\n')}`;
205
- } catch (e: any) {
206
- return `Error: ${e.message}`;
209
+ } catch (e: unknown) {
210
+ return `Error: ${errMsg(e)}`;
207
211
  }
208
212
  }),
209
213
  );
@@ -227,9 +231,10 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
227
231
  { cwd: process.cwd() },
228
232
  );
229
233
  return stdout.trim() || `No matches for '${args.pattern}'`;
230
- } catch (e: any) {
231
- if (e.code === 1) return `No matches for '${args.pattern}'`;
232
- return `Error: ${e.message}`;
234
+ } catch (e: unknown) {
235
+ const err = e as { code?: number; message?: string };
236
+ if (err.code === 1) return `No matches for '${args.pattern}'`;
237
+ return `Error: ${errMsg(e)}`;
233
238
  }
234
239
  }),
235
240
  );
@@ -256,8 +261,9 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
256
261
  if (stdout.trim()) result += `STDOUT:\n${stdout.trim()}`;
257
262
  if (stderr.trim()) result += `${result ? '\n' : ''}STDERR:\n${stderr.trim()}`;
258
263
  return result || '(no output)';
259
- } catch (e: any) {
260
- return `Error (exit ${e.code || '?'}): ${e.message}\nSTDOUT:\n${e.stdout || ''}\nSTDERR:\n${e.stderr || ''}`;
264
+ } catch (e: unknown) {
265
+ const err = e as { code?: number; message?: string; stdout?: string; stderr?: string };
266
+ return `Error (exit ${err.code || '?'}): ${errMsg(e)}\nSTDOUT:\n${err.stdout || ''}\nSTDERR:\n${err.stderr || ''}`;
261
267
  }
262
268
  }),
263
269
  );
@@ -319,8 +325,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
319
325
  return results.map((r, i) =>
320
326
  `${i + 1}. ${r.title}\n URL: ${r.url}\n ${r.snippet}`,
321
327
  ).join('\n\n');
322
- } catch (e: any) {
323
- return `Error: ${e.message}`;
328
+ } catch (e: unknown) {
329
+ return `Error: ${errMsg(e)}`;
324
330
  }
325
331
  }),
326
332
  );
@@ -349,8 +355,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
349
355
  .trim();
350
356
  const maxLen = 20 * 1024;
351
357
  return text.length > maxLen ? text.slice(0, maxLen) + '\n...(truncated)' : text;
352
- } catch (e: any) {
353
- return `Error: ${e.message}`;
358
+ } catch (e: unknown) {
359
+ return `Error: ${errMsg(e)}`;
354
360
  }
355
361
  }),
356
362
  );
@@ -375,8 +381,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
375
381
  return `${status} ${i + 1}. ${item.title}${item.detail ? ' — ' + item.detail : ''}`;
376
382
  });
377
383
  return `📋 Tasks (${data.items.filter((i: any) => i.status === 'done').length}/${data.items.length} done):\n${lines.join('\n')}`;
378
- } catch (e: any) {
379
- return `Error: ${e.message}`;
384
+ } catch (e: unknown) {
385
+ return `Error: ${errMsg(e)}`;
380
386
  }
381
387
  }),
382
388
  );
@@ -388,7 +394,7 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
388
394
  .keyword('创建计划', '更新任务', '标记完成', 'todo')
389
395
  .tag('plan', 'todo')
390
396
  .kind('plan')
391
- .param('items', { type: 'array', description: '任务列表 [{title, detail?, status: pending|in-progress|done}]' } as any, true)
397
+ .param('items', { type: 'array', description: '任务列表 [{title, detail?, status: pending|in-progress|done}]' } as PropertySchema<unknown[]>, true)
392
398
  .param('chat_id', { type: 'string', description: '聊天范围(可选)' })
393
399
  .execute(async (args) => {
394
400
  try {
@@ -399,8 +405,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
399
405
  await fs.promises.writeFile(todoPath, JSON.stringify(data, null, 2), 'utf-8');
400
406
  const done = args.items.filter((i: any) => i.status === 'done').length;
401
407
  return `✅ Tasks updated (${done}/${args.items.length} done)`;
402
- } catch (e: any) {
403
- return `Error: ${e.message}`;
408
+ } catch (e: unknown) {
409
+ return `Error: ${errMsg(e)}`;
404
410
  }
405
411
  }),
406
412
  );
@@ -421,8 +427,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
421
427
  : path.join(DATA_DIR, 'groups', args.chat_id || 'default', 'AGENTS.md');
422
428
  if (!fs.existsSync(memPath)) return 'No memory stored yet.';
423
429
  return await fs.promises.readFile(memPath, 'utf-8');
424
- } catch (e: any) {
425
- return `Error: ${e.message}`;
430
+ } catch (e: unknown) {
431
+ return `Error: ${errMsg(e)}`;
426
432
  }
427
433
  }),
428
434
  );
@@ -445,8 +451,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
445
451
  await fs.promises.mkdir(path.dirname(memPath), { recursive: true });
446
452
  await fs.promises.writeFile(memPath, args.content, 'utf-8');
447
453
  return `✅ Memory saved (${args.scope || 'chat'} scope)`;
448
- } catch (e: any) {
449
- return `Error: ${e.message}`;
454
+ } catch (e: unknown) {
455
+ return `Error: ${errMsg(e)}`;
450
456
  }
451
457
  }),
452
458
  );
@@ -478,8 +484,8 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
478
484
  }
479
485
  }
480
486
  return `Skill '${args.name}' not found. Check skills/ directory.`;
481
- } catch (e: any) {
482
- return `Error: ${e.message}`;
487
+ } catch (e: unknown) {
488
+ return `Error: ${errMsg(e)}`;
483
489
  }
484
490
  }),
485
491
  );
@@ -504,15 +510,15 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
504
510
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
505
511
  if (!fmMatch) return 'Error: 无效的 SKILL.md 文件(缺少 frontmatter)';
506
512
 
507
- let yaml: any;
513
+ let jsYaml: any;
508
514
  try {
509
- yaml = await import('yaml');
510
- if (yaml.default) yaml = yaml.default;
515
+ jsYaml = await import('js-yaml');
516
+ if (jsYaml.default) jsYaml = jsYaml.default;
511
517
  } catch {
512
518
  return 'Error: 无法加载 yaml 解析器';
513
519
  }
514
520
 
515
- const metadata = yaml.parse(fmMatch[1]);
521
+ const metadata = jsYaml.load(fmMatch[1]);
516
522
  if (!metadata?.name) return 'Error: SKILL.md 缺少 name 字段';
517
523
 
518
524
  const skillName: string = metadata.name;
@@ -523,13 +529,12 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
523
529
 
524
530
  logger.info(`技能已安装: ${skillName} → ${skillPath}`);
525
531
  return `✅ 技能「${skillName}」已安装到 ${skillPath}。现在可以用 activate_skill("${skillName}") 激活它。`;
526
- } catch (e: any) {
527
- return `Error: ${e.message}`;
532
+ } catch (e: unknown) {
533
+ return `Error: ${errMsg(e)}`;
528
534
  }
529
535
  }),
530
536
  );
531
537
 
532
- logger.info(`已创建 ${tools.length} 个内置系统工具`);
533
538
  return tools;
534
539
  }
535
540
 
@@ -539,14 +544,14 @@ export function createBuiltinTools(options?: BuiltinToolsOptions): ZhinTool[] {
539
544
  async function checkSkillDeps(content: string): Promise<string> {
540
545
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
541
546
  if (!fmMatch) return '';
542
- let yaml: any;
547
+ let jsYaml: any;
543
548
  try {
544
- yaml = await import('yaml');
545
- if (yaml.default) yaml = yaml.default;
549
+ jsYaml = await import('js-yaml');
550
+ if (jsYaml.default) jsYaml = jsYaml.default;
546
551
  } catch {
547
552
  return '';
548
553
  }
549
- const metadata = yaml.parse(fmMatch[1]);
554
+ const metadata = jsYaml.load(fmMatch[1]);
550
555
  if (!metadata) return '';
551
556
  const compat = metadata.compatibility || {};
552
557
  const deps = compat.deps || metadata.deps;
@@ -708,17 +713,16 @@ export async function discoverWorkspaceSkills(): Promise<SkillMeta[]> {
708
713
  continue;
709
714
  }
710
715
 
711
- // 动态导入 yaml,使用 .default 兼容 ESM 模块
712
- let yaml: any;
716
+ let jsYaml: any;
713
717
  try {
714
- yaml = await import('yaml');
715
- if (yaml.default) yaml = yaml.default;
718
+ jsYaml = await import('js-yaml');
719
+ if (jsYaml.default) jsYaml = jsYaml.default;
716
720
  } catch (e) {
717
- logger.warn(`Unable to import yaml module: ${e}`);
721
+ logger.warn(`Unable to import js-yaml module: ${e}`);
718
722
  continue;
719
723
  }
720
724
 
721
- const metadata = yaml.parse(match[1]);
725
+ const metadata = jsYaml.load(match[1]);
722
726
  if (!metadata || !metadata.name || !metadata.description) {
723
727
  logger.debug(`Skill文件 ${skillMdPath} 缺少必需的 name/description 字段`);
724
728
  continue;
package/src/compaction.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * 5. 降级策略:摘要失败时使用更粗糙的摘要
13
13
  */
14
14
 
15
- import { Logger } from '@zhin.js/logger';
15
+ import { Logger } from '@zhin.js/core';
16
16
  import type { AIProvider, ChatMessage } from '@zhin.js/core';
17
17
 
18
18
  const logger = new Logger(null, 'Compaction');
@@ -9,7 +9,7 @@
9
9
  * - 支持多平台、多场景
10
10
  */
11
11
 
12
- import { Logger } from '@zhin.js/logger';
12
+ import { Logger } from '@zhin.js/core';
13
13
  import type { ChatMessage, AIProvider } from '@zhin.js/core';
14
14
 
15
15
  const logger = new Logger(null, 'ContextManager');
@@ -30,7 +30,7 @@
30
30
  * → 不连续:丢弃 summary,仅用 [window]
31
31
  */
32
32
 
33
- import { Logger } from '@zhin.js/logger';
33
+ import { Logger } from '@zhin.js/core';
34
34
  import type { AIProvider, ChatMessage } from '@zhin.js/core';
35
35
 
36
36
  const logger = new Logger(null, 'ConvMemory');
@@ -316,6 +316,13 @@ export class ConversationMemory {
316
316
  private topicStates: Map<string, TopicState> = new Map();
317
317
  /** per-session 轮次缓存(避免每次查数据库) */
318
318
  private roundCache: Map<string, number> = new Map();
319
+ /** 各 session 最后活跃时间(用于淘汰) */
320
+ private lastAccess: Map<string, number> = new Map();
321
+
322
+ /** 内存缓存上限:超过此数量时淘汰过期条目 */
323
+ private static readonly MAX_TRACKED_SESSIONS = 5000;
324
+ /** 缓存过期时间:24 小时未访问即淘汰 */
325
+ private static readonly SESSION_CACHE_TTL = 24 * 60 * 60 * 1000;
319
326
 
320
327
  constructor(config?: ConversationMemoryConfig) {
321
328
  this.store = new MemoryStore();
@@ -345,6 +352,9 @@ export class ConversationMemory {
345
352
  userContent: string,
346
353
  assistantContent: string,
347
354
  ): Promise<void> {
355
+ this.lastAccess.set(sessionId, Date.now());
356
+ this.pruneStaleSessionCaches();
357
+
348
358
  // 优先用缓存,首次才查数据库
349
359
  const cached = this.roundCache.get(sessionId);
350
360
  let currentRound = cached != null ? cached + 1 : (await this.store.getMaxRound(sessionId)) + 1;
@@ -765,10 +775,42 @@ export class ConversationMemory {
765
775
 
766
776
  // ── 生命周期 ──
767
777
 
778
+ /**
779
+ * 淘汰长时间未访问的 session 缓存,防止 topicStates/roundCache 无限增长。
780
+ * 触发条件:Map size 超过 MAX_TRACKED_SESSIONS。
781
+ */
782
+ private pruneStaleSessionCaches(): void {
783
+ if (this.lastAccess.size <= ConversationMemory.MAX_TRACKED_SESSIONS) return;
784
+
785
+ const now = Date.now();
786
+ const ttl = ConversationMemory.SESSION_CACHE_TTL;
787
+ for (const [sid, ts] of this.lastAccess) {
788
+ if (now - ts > ttl) {
789
+ this.topicStates.delete(sid);
790
+ this.roundCache.delete(sid);
791
+ this.lastAccess.delete(sid);
792
+ this.summarizing.delete(sid);
793
+ }
794
+ }
795
+
796
+ // 如果 TTL 淘汰后仍超限,按 LRU 删除最旧的
797
+ if (this.lastAccess.size > ConversationMemory.MAX_TRACKED_SESSIONS) {
798
+ const entries = [...this.lastAccess.entries()].sort((a, b) => a[1] - b[1]);
799
+ const toRemove = entries.slice(0, entries.length - ConversationMemory.MAX_TRACKED_SESSIONS);
800
+ for (const [sid] of toRemove) {
801
+ this.topicStates.delete(sid);
802
+ this.roundCache.delete(sid);
803
+ this.lastAccess.delete(sid);
804
+ this.summarizing.delete(sid);
805
+ }
806
+ }
807
+ }
808
+
768
809
  dispose(): void {
769
810
  this.store.dispose();
770
811
  this.summarizing.clear();
771
812
  this.topicStates.clear();
772
813
  this.roundCache.clear();
814
+ this.lastAccess.clear();
773
815
  }
774
816
  }
@@ -12,7 +12,7 @@
12
12
  import * as fs from 'fs';
13
13
  import * as path from 'path';
14
14
  import { Cron, ZhinTool } from '@zhin.js/core';
15
- import { Logger } from '@zhin.js/logger';
15
+ import { Logger } from '@zhin.js/core';
16
16
 
17
17
  const logger = new Logger(null, 'cron-engine');
18
18
 
@@ -197,10 +197,12 @@ export class PersistentCronEngine {
197
197
  * 卸载所有由本引擎注册的定时任务(用于 dispose)
198
198
  */
199
199
  unload(): void {
200
- for (const dispose of this.disposes.values()) {
200
+ for (const [id, dispose] of this.disposes) {
201
201
  try {
202
202
  dispose();
203
- } catch (_) {}
203
+ } catch (e) {
204
+ logger.warn(`Cron dispose failed for ${id}:`, e);
205
+ }
204
206
  }
205
207
  this.disposes.clear();
206
208
  }
package/src/follow-up.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * └──────────────────────────────────────────────────────────────────┘
15
15
  */
16
16
 
17
- import { Logger } from '@zhin.js/logger';
17
+ import { Logger } from '@zhin.js/core';
18
18
 
19
19
  const logger = new Logger(null, 'FollowUp');
20
20
 
package/src/hooks.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * 钩子按注册顺序执行,错误不会中断其他钩子。
14
14
  */
15
15
 
16
- import { Logger } from '@zhin.js/logger';
16
+ import { Logger } from '@zhin.js/core';
17
17
 
18
18
  const logger = new Logger(null, 'AI-Hooks');
19
19
 
package/src/index.ts CHANGED
@@ -167,3 +167,13 @@ export {
167
167
  } from './tools.js';
168
168
 
169
169
  export { initAgentModule } from './init.js';
170
+
171
+ export {
172
+ MemoryStorageBackend,
173
+ DatabaseStorageBackend,
174
+ createSwappableBackend,
175
+ } from './storage.js';
176
+ export type {
177
+ StorageBackend,
178
+ DbModel,
179
+ } from './storage.js';
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Create ZhinAgent global brain and wire up sub-systems
3
+ * (follow-up sender, subagent manager, cron engine, scheduler).
4
+ */
5
+ import * as path from 'path';
6
+ import { getPlugin, Scheduler, getScheduler, setScheduler, type MessageType, type SendOptions } from '@zhin.js/core';
7
+ import { ZhinAgent } from '../zhin-agent/index.js';
8
+ import { createBuiltinTools } from '../builtin-tools.js';
9
+ import { resolveSkillInstructionMaxChars, DEFAULT_CONFIG } from '../zhin-agent/config.js';
10
+ import { PersistentCronEngine, setCronManager } from '../cron-engine.js';
11
+ import type { AIServiceRefs } from './shared-refs.js';
12
+
13
+ export function createZhinAgentContext(refs: AIServiceRefs): void {
14
+ const plugin = getPlugin();
15
+ const { useContext, root, logger } = plugin;
16
+
17
+ useContext('ai', (ai) => {
18
+ if (!ai.isReady()) {
19
+ logger.warn('AI Service not ready, ZhinAgent not created');
20
+ return;
21
+ }
22
+
23
+ const provider = ai.getProvider();
24
+ const agentConfig = ai.getAgentConfig();
25
+ const agent = new ZhinAgent(provider, agentConfig);
26
+ refs.zhinAgent = agent;
27
+
28
+ const skillRegistry = root.inject('skill');
29
+ if (skillRegistry) agent.setSkillRegistry(skillRegistry);
30
+
31
+ // Follow-up reminder sender
32
+ agent.setFollowUpSender(async (record) => {
33
+ const adapter = root.inject(record.platform) as { sendMessage?: (opts: SendOptions) => Promise<string> } | undefined;
34
+ if (!adapter || typeof adapter.sendMessage !== 'function') {
35
+ logger.warn(`[跟进提醒] 找不到适配器: ${record.platform}`);
36
+ return;
37
+ }
38
+ await adapter.sendMessage({
39
+ context: record.platform,
40
+ bot: record.bot_id,
41
+ id: record.scene_id,
42
+ type: record.scene_type as MessageType,
43
+ content: `⏰ 定时提醒:${record.message}`,
44
+ });
45
+ });
46
+
47
+ // Subagent manager for background tasks
48
+ agent.initSubagentManager(() => {
49
+ const modelName = provider.models[0] || '';
50
+ const fullConfig = { ...DEFAULT_CONFIG, ...agentConfig } as Required<import('../zhin-agent/config.js').ZhinAgentConfig>;
51
+ const zhinTools = createBuiltinTools({ skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullConfig, modelName) });
52
+ return zhinTools.map(zt => {
53
+ const t = zt.toTool();
54
+ return {
55
+ name: t.name,
56
+ description: t.description,
57
+ parameters: t.parameters,
58
+ execute: t.execute as (args: Record<string, any>) => Promise<unknown>,
59
+ tags: t.tags,
60
+ keywords: t.keywords,
61
+ };
62
+ });
63
+ });
64
+ agent.setSubagentSender(async (origin, content) => {
65
+ const adapter = root.inject(origin.platform) as { sendMessage?: (opts: SendOptions) => Promise<string> } | undefined;
66
+ if (!adapter || typeof adapter.sendMessage !== 'function') {
67
+ logger.warn(`[子任务] 找不到适配器: ${origin.platform}`);
68
+ return;
69
+ }
70
+ await adapter.sendMessage({
71
+ context: origin.platform,
72
+ bot: origin.botId,
73
+ id: origin.sceneId,
74
+ type: origin.sceneType as MessageType,
75
+ content,
76
+ });
77
+ });
78
+
79
+ // Persistent cron engine
80
+ let cronEngine: PersistentCronEngine | null = null;
81
+ const cronFeature = root.inject('cron') as import('@zhin.js/core').CronFeature | undefined;
82
+ if (cronFeature && typeof cronFeature.add === 'function') {
83
+ const dataDir = path.join(process.cwd(), 'data');
84
+ const addCron: import('../cron-engine.js').AddCronFn = (c) => cronFeature.add(c, 'cron-engine');
85
+ const runner = async (prompt: string) => {
86
+ if (!refs.zhinAgent) return;
87
+ await refs.zhinAgent.process(prompt, {
88
+ platform: 'cron',
89
+ senderId: 'system',
90
+ sceneId: 'cron',
91
+ });
92
+ };
93
+ cronEngine = new PersistentCronEngine({ dataDir, addCron, runner });
94
+ cronEngine.load();
95
+ setCronManager({ cronFeature, engine: cronEngine });
96
+ }
97
+
98
+ // Unified scheduler (at/every/cron + Heartbeat)
99
+ const dataDir = path.join(process.cwd(), 'data');
100
+ const scheduler = new Scheduler({
101
+ storePath: path.join(dataDir, 'scheduler-jobs.json'),
102
+ workspace: process.cwd(),
103
+ onJob: async (job) => {
104
+ if (!refs.zhinAgent) return;
105
+ await refs.zhinAgent.process(job.payload.message, {
106
+ platform: 'cron',
107
+ senderId: 'system',
108
+ sceneId: 'scheduler',
109
+ });
110
+ },
111
+ heartbeatEnabled: true,
112
+ heartbeatIntervalMs: 30 * 60 * 1000,
113
+ });
114
+ setScheduler(scheduler);
115
+ scheduler.start().catch((e) => logger.warn('Scheduler start failed: ' + (e as Error).message));
116
+
117
+ logger.debug('ZhinAgent created');
118
+ return () => {
119
+ setCronManager(null);
120
+ if (cronEngine) {
121
+ cronEngine.unload();
122
+ cronEngine = null;
123
+ }
124
+ const s = getScheduler();
125
+ if (s) {
126
+ s.stop();
127
+ setScheduler(null);
128
+ }
129
+ agent.dispose();
130
+ refs.zhinAgent = null;
131
+ };
132
+ });
133
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Register AIService as a plugin context.
3
+ */
4
+ import './types.js';
5
+ import { getPlugin, type Plugin } from '@zhin.js/core';
6
+ import type { AIConfig } from '@zhin.js/core';
7
+ import { AIService } from '../service.js';
8
+ import type { AIServiceRefs } from './shared-refs.js';
9
+
10
+ export function registerAIService(refs: AIServiceRefs): void {
11
+ const plugin = getPlugin();
12
+ const { provide, root, logger } = plugin;
13
+
14
+ provide<'ai'>({
15
+ name: 'ai',
16
+ description: 'AI Service - Multi-model LLM integration',
17
+ async mounted(_p: Plugin) {
18
+ const configService = root.inject('config');
19
+ const appConfig =
20
+ configService?.getPrimary<{ ai?: AIConfig }>() || {};
21
+ const config = appConfig.ai || {};
22
+
23
+ if (config.enabled === false) {
24
+ logger.info('AI Service is disabled');
25
+ return undefined as unknown as AIService;
26
+ }
27
+
28
+ const service = new AIService(config);
29
+ refs.aiService = service;
30
+ service.setPlugin(root);
31
+
32
+ const providers = service.listProviders();
33
+ if (providers.length === 0) {
34
+ logger.warn(
35
+ 'No AI providers configured. Please add API keys in zhin.config (yml/json/toml)',
36
+ );
37
+ } else {
38
+ logger.info(
39
+ `AI Service started with providers: ${providers.join(', ')}`,
40
+ );
41
+ }
42
+
43
+ return service;
44
+ },
45
+ async dispose(service) {
46
+ if (service) {
47
+ service.dispose();
48
+ refs.aiService = null;
49
+ logger.info('AI Service stopped');
50
+ }
51
+ },
52
+ });
53
+ }