@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.
- package/CHANGELOG.md +8 -0
- package/README.md +226 -0
- package/lib/agent.d.ts +9 -7
- package/lib/agent.d.ts.map +1 -1
- package/lib/agent.js +63 -29
- package/lib/agent.js.map +1 -1
- package/lib/bootstrap.js +1 -1
- package/lib/bootstrap.js.map +1 -1
- package/lib/builtin-tools.d.ts.map +1 -1
- package/lib/builtin-tools.js +34 -31
- package/lib/builtin-tools.js.map +1 -1
- package/lib/compaction.js +1 -1
- package/lib/compaction.js.map +1 -1
- package/lib/context-manager.js +1 -1
- package/lib/context-manager.js.map +1 -1
- package/lib/conversation-memory.d.ts +11 -0
- package/lib/conversation-memory.d.ts.map +1 -1
- package/lib/conversation-memory.js +39 -1
- package/lib/conversation-memory.js.map +1 -1
- package/lib/cron-engine.d.ts.map +1 -1
- package/lib/cron-engine.js +5 -3
- package/lib/cron-engine.js.map +1 -1
- package/lib/follow-up.js +1 -1
- package/lib/follow-up.js.map +1 -1
- package/lib/hooks.js +1 -1
- package/lib/hooks.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init/create-zhin-agent.d.ts +3 -0
- package/lib/init/create-zhin-agent.d.ts.map +1 -0
- package/lib/init/create-zhin-agent.js +127 -0
- package/lib/init/create-zhin-agent.js.map +1 -0
- package/lib/init/register-ai-service.d.ts +7 -0
- package/lib/init/register-ai-service.d.ts.map +1 -0
- package/lib/init/register-ai-service.js +42 -0
- package/lib/init/register-ai-service.js.map +1 -0
- package/lib/init/register-ai-trigger.d.ts +7 -0
- package/lib/init/register-ai-trigger.d.ts.map +1 -0
- package/lib/init/register-ai-trigger.js +163 -0
- package/lib/init/register-ai-trigger.js.map +1 -0
- package/lib/init/register-builtin-tools.d.ts +3 -0
- package/lib/init/register-builtin-tools.d.ts.map +1 -0
- package/lib/init/register-builtin-tools.js +179 -0
- package/lib/init/register-builtin-tools.js.map +1 -0
- package/lib/init/register-db-models.d.ts +2 -0
- package/lib/init/register-db-models.d.ts.map +1 -0
- package/lib/init/register-db-models.js +28 -0
- package/lib/init/register-db-models.js.map +1 -0
- package/lib/init/register-db-upgrade.d.ts +10 -0
- package/lib/init/register-db-upgrade.d.ts.map +1 -0
- package/lib/init/register-db-upgrade.js +67 -0
- package/lib/init/register-db-upgrade.js.map +1 -0
- package/lib/init/register-management-tools.d.ts +6 -0
- package/lib/init/register-management-tools.d.ts.map +1 -0
- package/lib/init/register-management-tools.js +68 -0
- package/lib/init/register-management-tools.js.map +1 -0
- package/lib/init/register-message-recorder.d.ts +3 -0
- package/lib/init/register-message-recorder.d.ts.map +1 -0
- package/lib/init/register-message-recorder.js +27 -0
- package/lib/init/register-message-recorder.js.map +1 -0
- package/lib/init/register-tool-service.d.ts +2 -0
- package/lib/init/register-tool-service.d.ts.map +1 -0
- package/lib/init/register-tool-service.js +9 -0
- package/lib/init/register-tool-service.js.map +1 -0
- package/lib/init/shared-refs.d.ts +14 -0
- package/lib/init/shared-refs.d.ts.map +1 -0
- package/lib/init/shared-refs.js +7 -0
- package/lib/init/shared-refs.js.map +1 -0
- package/lib/init/types.d.ts +15 -0
- package/lib/init/types.d.ts.map +1 -0
- package/lib/init/types.js +7 -0
- package/lib/init/types.js.map +1 -0
- package/lib/init.d.ts +14 -17
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +34 -670
- package/lib/init.js.map +1 -1
- package/lib/rate-limiter.js +1 -1
- package/lib/rate-limiter.js.map +1 -1
- package/lib/service.js +2 -2
- package/lib/service.js.map +1 -1
- package/lib/session.d.ts +7 -0
- package/lib/session.d.ts.map +1 -1
- package/lib/session.js +26 -4
- package/lib/session.js.map +1 -1
- package/lib/storage.d.ts +68 -0
- package/lib/storage.d.ts.map +1 -0
- package/lib/storage.js +105 -0
- package/lib/storage.js.map +1 -0
- package/lib/subagent.js +1 -1
- package/lib/subagent.js.map +1 -1
- package/lib/tools.d.ts.map +1 -1
- package/lib/tools.js +8 -9
- package/lib/tools.js.map +1 -1
- package/lib/user-profile.js +1 -1
- package/lib/user-profile.js.map +1 -1
- package/lib/zhin-agent/builtin-tools.js.map +1 -1
- package/lib/zhin-agent/config.d.ts +1 -1
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/index.js +1 -1
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/tool-collector.js +3 -3
- package/lib/zhin-agent/tool-collector.js.map +1 -1
- package/package.json +4 -3
- package/src/agent.ts +53 -30
- package/src/bootstrap.ts +1 -1
- package/src/builtin-tools.ts +44 -40
- package/src/compaction.ts +1 -1
- package/src/context-manager.ts +1 -1
- package/src/conversation-memory.ts +43 -1
- package/src/cron-engine.ts +5 -3
- package/src/follow-up.ts +1 -1
- package/src/hooks.ts +1 -1
- package/src/index.ts +10 -0
- package/src/init/create-zhin-agent.ts +133 -0
- package/src/init/register-ai-service.ts +53 -0
- package/src/init/register-ai-trigger.ts +179 -0
- package/src/init/register-builtin-tools.ts +177 -0
- package/src/init/register-db-models.ts +31 -0
- package/src/init/register-db-upgrade.ts +77 -0
- package/src/init/register-management-tools.ts +71 -0
- package/src/init/register-message-recorder.ts +31 -0
- package/src/init/register-tool-service.ts +9 -0
- package/src/init/shared-refs.ts +20 -0
- package/src/init/types.ts +18 -0
- package/src/init.ts +35 -735
- package/src/rate-limiter.ts +1 -1
- package/src/service.ts +4 -4
- package/src/session.ts +26 -4
- package/src/storage.ts +135 -0
- package/src/subagent.ts +1 -1
- package/src/tools.ts +7 -10
- package/src/user-profile.ts +1 -1
- package/src/zhin-agent/builtin-tools.ts +2 -2
- package/src/zhin-agent/config.ts +3 -3
- package/src/zhin-agent/index.ts +1 -1
- package/src/zhin-agent/tool-collector.ts +8 -8
package/src/builtin-tools.ts
CHANGED
|
@@ -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/
|
|
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:
|
|
206
|
-
return `Error: ${e
|
|
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:
|
|
231
|
-
|
|
232
|
-
return `
|
|
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:
|
|
260
|
-
|
|
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:
|
|
323
|
-
return `Error: ${e
|
|
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:
|
|
353
|
-
return `Error: ${e
|
|
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:
|
|
379
|
-
return `Error: ${e
|
|
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
|
|
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:
|
|
403
|
-
return `Error: ${e
|
|
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:
|
|
425
|
-
return `Error: ${e
|
|
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:
|
|
449
|
-
return `Error: ${e
|
|
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:
|
|
482
|
-
return `Error: ${e
|
|
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
|
|
513
|
+
let jsYaml: any;
|
|
508
514
|
try {
|
|
509
|
-
|
|
510
|
-
if (
|
|
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 =
|
|
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:
|
|
527
|
-
return `Error: ${e
|
|
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
|
|
547
|
+
let jsYaml: any;
|
|
543
548
|
try {
|
|
544
|
-
|
|
545
|
-
if (
|
|
549
|
+
jsYaml = await import('js-yaml');
|
|
550
|
+
if (jsYaml.default) jsYaml = jsYaml.default;
|
|
546
551
|
} catch {
|
|
547
552
|
return '';
|
|
548
553
|
}
|
|
549
|
-
const metadata =
|
|
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
|
-
|
|
712
|
-
let yaml: any;
|
|
716
|
+
let jsYaml: any;
|
|
713
717
|
try {
|
|
714
|
-
|
|
715
|
-
if (
|
|
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 =
|
|
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
package/src/context-manager.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* → 不连续:丢弃 summary,仅用 [window]
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import { Logger } from '@zhin.js/
|
|
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
|
}
|
package/src/cron-engine.ts
CHANGED
|
@@ -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/
|
|
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
|
|
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
package/src/hooks.ts
CHANGED
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
|
+
}
|