@zhin.js/core 1.0.32 → 1.0.34
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 +18 -0
- package/lib/ai/agent.d.ts.map +1 -1
- package/lib/ai/agent.js +15 -2
- package/lib/ai/agent.js.map +1 -1
- package/lib/ai/bootstrap.d.ts +11 -2
- package/lib/ai/bootstrap.d.ts.map +1 -1
- package/lib/ai/bootstrap.js +46 -2
- package/lib/ai/bootstrap.js.map +1 -1
- package/lib/ai/builtin-tools.d.ts +28 -6
- package/lib/ai/builtin-tools.d.ts.map +1 -1
- package/lib/ai/builtin-tools.js +265 -76
- package/lib/ai/builtin-tools.js.map +1 -1
- package/lib/ai/index.d.ts +9 -1
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +8 -0
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/init.d.ts.map +1 -1
- package/lib/ai/init.js +84 -3
- package/lib/ai/init.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts +7 -0
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +3 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/ollama.d.ts +10 -0
- package/lib/ai/providers/ollama.d.ts.map +1 -1
- package/lib/ai/providers/ollama.js +11 -3
- package/lib/ai/providers/ollama.js.map +1 -1
- package/lib/ai/providers/openai.d.ts +7 -0
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +3 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/service.d.ts +4 -0
- package/lib/ai/service.d.ts.map +1 -1
- package/lib/ai/service.js +7 -0
- package/lib/ai/service.js.map +1 -1
- package/lib/ai/subagent.d.ts +50 -0
- package/lib/ai/subagent.d.ts.map +1 -0
- package/lib/ai/subagent.js +144 -0
- package/lib/ai/subagent.js.map +1 -0
- package/lib/ai/types.d.ts +25 -5
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/ai/zhin-agent-builtin-tools.d.ts +17 -0
- package/lib/ai/zhin-agent-builtin-tools.d.ts.map +1 -0
- package/lib/ai/zhin-agent-builtin-tools.js +220 -0
- package/lib/ai/zhin-agent-builtin-tools.js.map +1 -0
- package/lib/ai/zhin-agent-config.d.ts +54 -0
- package/lib/ai/zhin-agent-config.d.ts.map +1 -0
- package/lib/ai/zhin-agent-config.js +76 -0
- package/lib/ai/zhin-agent-config.js.map +1 -0
- package/lib/ai/zhin-agent-exec-policy.d.ts +20 -0
- package/lib/ai/zhin-agent-exec-policy.d.ts.map +1 -0
- package/lib/ai/zhin-agent-exec-policy.js +71 -0
- package/lib/ai/zhin-agent-exec-policy.js.map +1 -0
- package/lib/ai/zhin-agent-prompt.d.ts +21 -0
- package/lib/ai/zhin-agent-prompt.d.ts.map +1 -0
- package/lib/ai/zhin-agent-prompt.js +116 -0
- package/lib/ai/zhin-agent-prompt.js.map +1 -0
- package/lib/ai/zhin-agent-tool-collector.d.ts +22 -0
- package/lib/ai/zhin-agent-tool-collector.d.ts.map +1 -0
- package/lib/ai/zhin-agent-tool-collector.js +218 -0
- package/lib/ai/zhin-agent-tool-collector.js.map +1 -0
- package/lib/ai/zhin-agent.d.ts +11 -155
- package/lib/ai/zhin-agent.d.ts.map +1 -1
- package/lib/ai/zhin-agent.js +84 -684
- package/lib/ai/zhin-agent.js.map +1 -1
- package/lib/component.d.ts.map +1 -1
- package/lib/component.js +19 -19
- package/lib/component.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/scheduler/index.d.ts +10 -0
- package/lib/scheduler/index.d.ts.map +1 -0
- package/lib/scheduler/index.js +12 -0
- package/lib/scheduler/index.js.map +1 -0
- package/lib/scheduler/scheduler.d.ts +49 -0
- package/lib/scheduler/scheduler.d.ts.map +1 -0
- package/lib/scheduler/scheduler.js +352 -0
- package/lib/scheduler/scheduler.js.map +1 -0
- package/lib/scheduler/types.d.ts +71 -0
- package/lib/scheduler/types.d.ts.map +1 -0
- package/lib/scheduler/types.js +8 -0
- package/lib/scheduler/types.js.map +1 -0
- package/lib/tool-zod.d.ts +28 -0
- package/lib/tool-zod.d.ts.map +1 -0
- package/lib/tool-zod.js +98 -0
- package/lib/tool-zod.js.map +1 -0
- package/package.json +9 -4
- package/src/ai/agent.ts +15 -2
- package/src/ai/bootstrap.ts +48 -2
- package/src/ai/builtin-tools.ts +283 -75
- package/src/ai/index.ts +19 -1
- package/src/ai/init.ts +85 -3
- package/src/ai/providers/anthropic.ts +3 -0
- package/src/ai/providers/ollama.ts +13 -3
- package/src/ai/providers/openai.ts +3 -0
- package/src/ai/service.ts +8 -0
- package/src/ai/subagent.ts +209 -0
- package/src/ai/types.ts +29 -2
- package/src/ai/zhin-agent-builtin-tools.ts +247 -0
- package/src/ai/zhin-agent-config.ts +113 -0
- package/src/ai/zhin-agent-exec-policy.ts +78 -0
- package/src/ai/zhin-agent-prompt.ts +136 -0
- package/src/ai/zhin-agent-tool-collector.ts +243 -0
- package/src/ai/zhin-agent.ts +113 -791
- package/src/component.ts +29 -28
- package/src/index.ts +1 -0
- package/src/scheduler/index.ts +28 -0
- package/src/scheduler/scheduler.ts +372 -0
- package/src/scheduler/types.ts +74 -0
- package/src/tool-zod.ts +115 -0
- package/tests/ai/subagent.test.ts +270 -0
package/src/ai/index.ts
CHANGED
|
@@ -9,12 +9,13 @@ export type {
|
|
|
9
9
|
AIConfig,
|
|
10
10
|
AIProvider,
|
|
11
11
|
ProviderConfig,
|
|
12
|
+
ProviderCapabilities,
|
|
13
|
+
OllamaProviderConfig,
|
|
12
14
|
ChatMessage,
|
|
13
15
|
ChatCompletionRequest,
|
|
14
16
|
ChatCompletionResponse,
|
|
15
17
|
ChatCompletionChunk,
|
|
16
18
|
ContentPart,
|
|
17
|
-
// 注意:ToolDefinition 已从 core/types.ts 导出,此处不重复导出
|
|
18
19
|
ToolCall,
|
|
19
20
|
MessageRole,
|
|
20
21
|
AgentTool,
|
|
@@ -52,6 +53,14 @@ export type {
|
|
|
52
53
|
export { ZhinAgent } from './zhin-agent.js';
|
|
53
54
|
export type { ZhinAgentConfig, OnChunkCallback } from './zhin-agent.js';
|
|
54
55
|
|
|
56
|
+
// ── ZhinAgent sub-modules (for advanced consumers) ──
|
|
57
|
+
export { PERM_MAP, DEFAULT_CONFIG as ZHIN_AGENT_DEFAULT_CONFIG, SECTION_SEP } from './zhin-agent-config.js';
|
|
58
|
+
export { checkExecPolicy, applyExecPolicyToTools, resolveExecAllowlist, EXEC_PRESETS } from './zhin-agent-exec-policy.js';
|
|
59
|
+
export { collectRelevantTools, toAgentTool } from './zhin-agent-tool-collector.js';
|
|
60
|
+
export { buildRichSystemPrompt, buildContextHint, buildEnhancedPersona, buildUserMessageWithHistory, contentToText } from './zhin-agent-prompt.js';
|
|
61
|
+
export type { RichSystemPromptContext } from './zhin-agent-prompt.js';
|
|
62
|
+
export { createChatHistoryTool, createUserProfileTool, createScheduleFollowUpTool, createSpawnTaskTool } from './zhin-agent-builtin-tools.js';
|
|
63
|
+
|
|
55
64
|
// ── Conversation Memory ──
|
|
56
65
|
export {
|
|
57
66
|
ConversationMemory,
|
|
@@ -74,6 +83,15 @@ export type { RateLimitConfig, RateLimitResult } from './rate-limiter.js';
|
|
|
74
83
|
export { FollowUpManager, AI_FOLLOWUP_MODEL } from './follow-up.js';
|
|
75
84
|
export type { FollowUpRecord, FollowUpSender } from './follow-up.js';
|
|
76
85
|
|
|
86
|
+
// ── Subagent ──
|
|
87
|
+
export { SubagentManager } from './subagent.js';
|
|
88
|
+
export type {
|
|
89
|
+
SubagentOrigin,
|
|
90
|
+
SubagentResultSender,
|
|
91
|
+
SpawnOptions,
|
|
92
|
+
SubagentManagerOptions,
|
|
93
|
+
} from './subagent.js';
|
|
94
|
+
|
|
77
95
|
// ── 持久化定时任务引擎 ──
|
|
78
96
|
export {
|
|
79
97
|
PersistentCronEngine,
|
package/src/ai/init.ts
CHANGED
|
@@ -29,7 +29,8 @@ import type { MessageDispatcherService } from '../built/dispatcher.js';
|
|
|
29
29
|
import type { SkillFeature } from '../built/skill.js';
|
|
30
30
|
import { AIService } from './service.js';
|
|
31
31
|
import { ZhinAgent } from './zhin-agent.js';
|
|
32
|
-
import { createBuiltinTools, discoverWorkspaceSkills, loadSoulPersona } from './builtin-tools.js';
|
|
32
|
+
import { createBuiltinTools, discoverWorkspaceSkills, loadSoulPersona, loadAlwaysSkillsContent, buildSkillsSummaryXML } from './builtin-tools.js';
|
|
33
|
+
import { resolveSkillInstructionMaxChars, DEFAULT_CONFIG } from './zhin-agent-config.js';
|
|
33
34
|
import { loadBootstrapFiles, buildContextFiles, buildBootstrapContextSection, loadToolsGuide } from './bootstrap.js';
|
|
34
35
|
import { triggerAIHook, createAIHookEvent } from './hooks.js';
|
|
35
36
|
import { SessionManager, createDatabaseSessionManager } from './session.js';
|
|
@@ -44,6 +45,7 @@ import { AI_MESSAGE_MODEL, AI_SUMMARY_MODEL } from './conversation-memory.js';
|
|
|
44
45
|
import { AI_USER_PROFILE_MODEL } from './user-profile.js';
|
|
45
46
|
import { AI_FOLLOWUP_MODEL } from './follow-up.js';
|
|
46
47
|
import { PersistentCronEngine, setCronManager, createCronTools } from './cron-engine.js';
|
|
48
|
+
import { Scheduler, getScheduler, setScheduler } from '../scheduler/index.js';
|
|
47
49
|
import { renderToPlainText, type OutputElement } from './output.js';
|
|
48
50
|
import type { AIConfig, ContentPart } from './types.js';
|
|
49
51
|
|
|
@@ -204,6 +206,38 @@ export function initAIModule(): void {
|
|
|
204
206
|
});
|
|
205
207
|
});
|
|
206
208
|
|
|
209
|
+
// 子任务管理器:让 AI 可以 spawn 后台子 agent 异步执行复杂任务
|
|
210
|
+
agent.initSubagentManager(() => {
|
|
211
|
+
const modelName = provider.models[0] || '';
|
|
212
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...agentConfig } as Required<import('./zhin-agent-config.js').ZhinAgentConfig>;
|
|
213
|
+
const zhinTools = createBuiltinTools({ skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullConfig, modelName) });
|
|
214
|
+
return zhinTools.map(zt => {
|
|
215
|
+
const t = zt.toTool();
|
|
216
|
+
return {
|
|
217
|
+
name: t.name,
|
|
218
|
+
description: t.description,
|
|
219
|
+
parameters: t.parameters as any,
|
|
220
|
+
execute: t.execute as (args: Record<string, any>) => Promise<any>,
|
|
221
|
+
tags: t.tags,
|
|
222
|
+
keywords: t.keywords,
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
agent.setSubagentSender(async (origin, content) => {
|
|
227
|
+
const adapter = root.inject(origin.platform as any) as any;
|
|
228
|
+
if (!adapter || typeof adapter.sendMessage !== 'function') {
|
|
229
|
+
logger.warn(`[子任务] 找不到适配器: ${origin.platform}`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
await adapter.sendMessage({
|
|
233
|
+
context: origin.platform,
|
|
234
|
+
bot: origin.botId,
|
|
235
|
+
id: origin.sceneId,
|
|
236
|
+
type: origin.sceneType as any,
|
|
237
|
+
content,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
207
241
|
// 持久化定时任务引擎:加载 data/cron-jobs.json,到点用 prompt 调用 Agent;并暴露给 AI 管理(list/add/remove/pause/resume)
|
|
208
242
|
let cronEngine: PersistentCronEngine | null = null;
|
|
209
243
|
const cronFeature = root.inject('cron' as any);
|
|
@@ -223,6 +257,26 @@ export function initAIModule(): void {
|
|
|
223
257
|
setCronManager({ cronFeature, engine: cronEngine });
|
|
224
258
|
}
|
|
225
259
|
|
|
260
|
+
// 统一调度器(at/every/cron + Heartbeat),持久化到 data/scheduler-jobs.json
|
|
261
|
+
const dataDir = path.join(process.cwd(), 'data');
|
|
262
|
+
const workspace = process.cwd();
|
|
263
|
+
const scheduler = new Scheduler({
|
|
264
|
+
storePath: path.join(dataDir, 'scheduler-jobs.json'),
|
|
265
|
+
workspace,
|
|
266
|
+
onJob: async (job) => {
|
|
267
|
+
if (!zhinAgentInstance) return;
|
|
268
|
+
await zhinAgentInstance.process(job.payload.message, {
|
|
269
|
+
platform: 'cron',
|
|
270
|
+
senderId: 'system',
|
|
271
|
+
sceneId: 'scheduler',
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
heartbeatEnabled: true,
|
|
275
|
+
heartbeatIntervalMs: 30 * 60 * 1000,
|
|
276
|
+
});
|
|
277
|
+
setScheduler(scheduler);
|
|
278
|
+
scheduler.start().catch((e) => logger.warn('Scheduler start failed: ' + (e as Error).message));
|
|
279
|
+
|
|
226
280
|
logger.debug('ZhinAgent created');
|
|
227
281
|
return () => {
|
|
228
282
|
setCronManager(null);
|
|
@@ -230,6 +284,11 @@ export function initAIModule(): void {
|
|
|
230
284
|
cronEngine.unload();
|
|
231
285
|
cronEngine = null;
|
|
232
286
|
}
|
|
287
|
+
const s = getScheduler();
|
|
288
|
+
if (s) {
|
|
289
|
+
s.stop();
|
|
290
|
+
setScheduler(null);
|
|
291
|
+
}
|
|
233
292
|
agent.dispose();
|
|
234
293
|
zhinAgentInstance = null;
|
|
235
294
|
};
|
|
@@ -534,8 +593,11 @@ export function initAIModule(): void {
|
|
|
534
593
|
useContext('ai', 'tool', (ai, toolService) => {
|
|
535
594
|
if (!ai || !toolService) return;
|
|
536
595
|
|
|
537
|
-
|
|
538
|
-
const
|
|
596
|
+
const provider = ai.getProvider();
|
|
597
|
+
const agentCfg = ai.getAgentConfig();
|
|
598
|
+
const fullCfg = { ...DEFAULT_CONFIG, ...agentCfg } as Required<import('./zhin-agent-config.js').ZhinAgentConfig>;
|
|
599
|
+
const modelName = provider.models[0] || '';
|
|
600
|
+
const builtinTools = createBuiltinTools({ skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullCfg, modelName) });
|
|
539
601
|
const disposers: (() => void)[] = [];
|
|
540
602
|
for (const tool of builtinTools) disposers.push(toolService.addTool(tool, root.name));
|
|
541
603
|
const cronTools = createCronTools();
|
|
@@ -632,6 +694,19 @@ export function initAIModule(): void {
|
|
|
632
694
|
logger.debug(`Bootstrap files not loaded: ${e.message}`);
|
|
633
695
|
}
|
|
634
696
|
|
|
697
|
+
// ── 第三步:常驻技能正文 + 技能 XML 摘要注入 Agent ──
|
|
698
|
+
try {
|
|
699
|
+
const skillsForContext = await discoverWorkspaceSkills();
|
|
700
|
+
const alwaysContent = await loadAlwaysSkillsContent(skillsForContext);
|
|
701
|
+
const skillsXml = buildSkillsSummaryXML(skillsForContext);
|
|
702
|
+
if (zhinAgentInstance) {
|
|
703
|
+
zhinAgentInstance.setActiveSkillsContext(alwaysContent);
|
|
704
|
+
zhinAgentInstance.setSkillsSummaryXML(skillsXml);
|
|
705
|
+
}
|
|
706
|
+
} catch (e: any) {
|
|
707
|
+
logger.debug(`Skills context not set: ${e.message}`);
|
|
708
|
+
}
|
|
709
|
+
|
|
635
710
|
// 触发 agent:bootstrap Hook
|
|
636
711
|
const skillFeature2 = (root as any).inject?.('skill') as SkillFeature | undefined;
|
|
637
712
|
await triggerAIHook(createAIHookEvent('agent', 'bootstrap', undefined, {
|
|
@@ -650,6 +725,13 @@ export function initAIModule(): void {
|
|
|
650
725
|
skillReloadDebounce = null;
|
|
651
726
|
try {
|
|
652
727
|
const count = await syncWorkspaceSkills();
|
|
728
|
+
const skillsForContext = await discoverWorkspaceSkills();
|
|
729
|
+
const alwaysContent = await loadAlwaysSkillsContent(skillsForContext);
|
|
730
|
+
const skillsXml = buildSkillsSummaryXML(skillsForContext);
|
|
731
|
+
if (zhinAgentInstance) {
|
|
732
|
+
zhinAgentInstance.setActiveSkillsContext(alwaysContent);
|
|
733
|
+
zhinAgentInstance.setSkillsSummaryXML(skillsXml);
|
|
734
|
+
}
|
|
653
735
|
await triggerAIHook(createAIHookEvent('agent', 'skills-reloaded', undefined, { skillCount: count }));
|
|
654
736
|
if (count >= 0) logger.info(`[技能热重载] 已更新,当前工作区技能数: ${count}`);
|
|
655
737
|
} catch (e: any) {
|
|
@@ -178,12 +178,15 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
178
178
|
'claude-3-sonnet-20240229',
|
|
179
179
|
'claude-3-haiku-20240307',
|
|
180
180
|
];
|
|
181
|
+
contextWindow: number;
|
|
182
|
+
capabilities = { vision: true, streaming: true, toolCalling: true, thinking: false };
|
|
181
183
|
|
|
182
184
|
private baseUrl: string;
|
|
183
185
|
private anthropicVersion: string;
|
|
184
186
|
|
|
185
187
|
constructor(config: AnthropicConfig = {}) {
|
|
186
188
|
super(config);
|
|
189
|
+
this.contextWindow = config.contextWindow ?? 200000;
|
|
187
190
|
this.baseUrl = config.baseUrl || 'https://api.anthropic.com';
|
|
188
191
|
this.anthropicVersion = config.anthropicVersion || '2023-06-01';
|
|
189
192
|
}
|
|
@@ -19,6 +19,8 @@ const logger = new Logger(null, 'Ollama');
|
|
|
19
19
|
export interface OllamaConfig extends ProviderConfig {
|
|
20
20
|
host?: string;
|
|
21
21
|
models?: string[];
|
|
22
|
+
/** Ollama 上下文窗口大小(token 数),默认 32768。影响多轮对话和技能指令的保持能力 */
|
|
23
|
+
num_ctx?: number;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -81,13 +83,17 @@ function toOllamaTools(tools?: ToolDefinition[]): any[] | undefined {
|
|
|
81
83
|
export class OllamaProvider extends BaseProvider {
|
|
82
84
|
name = 'ollama';
|
|
83
85
|
models: string[];
|
|
86
|
+
contextWindow: number;
|
|
87
|
+
capabilities = { vision: true, streaming: true, toolCalling: true, thinking: true };
|
|
84
88
|
|
|
85
89
|
private host: string;
|
|
90
|
+
private numCtx: number;
|
|
86
91
|
|
|
87
92
|
constructor(config: OllamaConfig = {}) {
|
|
88
93
|
super(config);
|
|
89
94
|
this.host = config.host || config.baseUrl || 'http://localhost:11434';
|
|
90
|
-
|
|
95
|
+
this.numCtx = config.contextWindow ?? config.num_ctx ?? 32768;
|
|
96
|
+
this.contextWindow = this.numCtx;
|
|
91
97
|
this.models = config.models?.length ? config.models : [
|
|
92
98
|
'llama3.3',
|
|
93
99
|
'llama3.2',
|
|
@@ -113,7 +119,9 @@ export class OllamaProvider extends BaseProvider {
|
|
|
113
119
|
model: request.model,
|
|
114
120
|
messages,
|
|
115
121
|
stream: false,
|
|
116
|
-
options: {
|
|
122
|
+
options: {
|
|
123
|
+
num_ctx: this.numCtx,
|
|
124
|
+
},
|
|
117
125
|
};
|
|
118
126
|
|
|
119
127
|
// think 参数:控制 qwen3 等模型的思考模式
|
|
@@ -186,7 +194,9 @@ export class OllamaProvider extends BaseProvider {
|
|
|
186
194
|
model: request.model,
|
|
187
195
|
messages,
|
|
188
196
|
stream: true,
|
|
189
|
-
options: {
|
|
197
|
+
options: {
|
|
198
|
+
num_ctx: this.numCtx,
|
|
199
|
+
},
|
|
190
200
|
};
|
|
191
201
|
|
|
192
202
|
if (request.think !== undefined) {
|
|
@@ -28,11 +28,14 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
28
28
|
'o1-preview',
|
|
29
29
|
'o3-mini',
|
|
30
30
|
];
|
|
31
|
+
contextWindow: number;
|
|
32
|
+
capabilities = { vision: true, streaming: true, toolCalling: true, thinking: false };
|
|
31
33
|
|
|
32
34
|
private baseUrl: string;
|
|
33
35
|
|
|
34
36
|
constructor(config: OpenAIConfig = {}) {
|
|
35
37
|
super(config);
|
|
38
|
+
this.contextWindow = config.contextWindow ?? 128000;
|
|
36
39
|
this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
|
|
37
40
|
|
|
38
41
|
if (config.organization) {
|
package/src/ai/service.ts
CHANGED
|
@@ -267,6 +267,14 @@ ${preExecutedData ? `\n已自动获取的数据:${preExecutedData}\n` : ''}
|
|
|
267
267
|
}
|
|
268
268
|
listProviders(): string[] { return Array.from(this.providers.keys()); }
|
|
269
269
|
|
|
270
|
+
getProviderCapabilities(name?: string): { contextWindow?: number; capabilities?: import('./types.js').ProviderCapabilities } {
|
|
271
|
+
const provider = this.getProvider(name);
|
|
272
|
+
return {
|
|
273
|
+
contextWindow: provider.contextWindow,
|
|
274
|
+
capabilities: provider.capabilities,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
270
278
|
async listModels(providerName?: string): Promise<{ provider: string; models: string[] }[]> {
|
|
271
279
|
const result: { provider: string; models: string[] }[] = [];
|
|
272
280
|
if (providerName) {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubagentManager — 后台子任务执行管理器
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 接收主 agent 发起的 spawn 请求,创建后台子 agent
|
|
6
|
+
* 2. 为子 agent 配备受限工具集(文件/Shell/网络,不含消息/spawn/技能)
|
|
7
|
+
* 3. 管理子 agent 生命周期(运行跟踪、完成/失败回告)
|
|
8
|
+
* 4. 完成后将结果投递回主流程,由主 agent 摘要后发送到原始频道
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import { Logger } from '@zhin.js/logger';
|
|
13
|
+
import type { AIProvider, AgentTool } from './types.js';
|
|
14
|
+
import { createAgent } from './agent.js';
|
|
15
|
+
import type { ZhinAgentConfig } from './zhin-agent-config.js';
|
|
16
|
+
import { applyExecPolicyToTools } from './zhin-agent-exec-policy.js';
|
|
17
|
+
|
|
18
|
+
const logger = new Logger(null, 'Subagent');
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// 类型
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
export interface SubagentOrigin {
|
|
25
|
+
platform: string;
|
|
26
|
+
botId: string;
|
|
27
|
+
sceneId: string;
|
|
28
|
+
senderId: string;
|
|
29
|
+
sceneType: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SpawnOptions {
|
|
33
|
+
task: string;
|
|
34
|
+
label?: string;
|
|
35
|
+
origin: SubagentOrigin;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SubagentResultSender = (origin: SubagentOrigin, content: string) => Promise<void>;
|
|
39
|
+
|
|
40
|
+
export interface SubagentManagerOptions {
|
|
41
|
+
provider: AIProvider;
|
|
42
|
+
workspace: string;
|
|
43
|
+
createTools: () => AgentTool[];
|
|
44
|
+
maxIterations?: number;
|
|
45
|
+
/** Exec policy config to enforce on subagent bash tools */
|
|
46
|
+
execPolicyConfig?: Required<ZhinAgentConfig>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// 子 agent 允许使用的工具名单
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
const SUBAGENT_ALLOWED_TOOLS = new Set([
|
|
54
|
+
'read_file',
|
|
55
|
+
'write_file',
|
|
56
|
+
'edit_file',
|
|
57
|
+
'list_dir',
|
|
58
|
+
'glob',
|
|
59
|
+
'grep',
|
|
60
|
+
'bash',
|
|
61
|
+
'web_search',
|
|
62
|
+
'web_fetch',
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// SubagentManager
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
export class SubagentManager {
|
|
70
|
+
private provider: AIProvider;
|
|
71
|
+
private workspace: string;
|
|
72
|
+
private createTools: () => AgentTool[];
|
|
73
|
+
private maxIterations: number;
|
|
74
|
+
private execPolicyConfig: Required<ZhinAgentConfig> | null;
|
|
75
|
+
private runningTasks: Map<string, AbortController> = new Map();
|
|
76
|
+
private resultSender: SubagentResultSender | null = null;
|
|
77
|
+
|
|
78
|
+
constructor(options: SubagentManagerOptions) {
|
|
79
|
+
this.provider = options.provider;
|
|
80
|
+
this.workspace = options.workspace;
|
|
81
|
+
this.createTools = options.createTools;
|
|
82
|
+
this.maxIterations = options.maxIterations ?? 15;
|
|
83
|
+
this.execPolicyConfig = options.execPolicyConfig ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setSender(sender: SubagentResultSender): void {
|
|
87
|
+
this.resultSender = sender;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async spawn(options: SpawnOptions): Promise<string> {
|
|
91
|
+
const taskId = randomUUID().slice(0, 8);
|
|
92
|
+
const displayLabel =
|
|
93
|
+
options.label ||
|
|
94
|
+
options.task.slice(0, 30) + (options.task.length > 30 ? '...' : '');
|
|
95
|
+
|
|
96
|
+
const abortController = new AbortController();
|
|
97
|
+
this.runningTasks.set(taskId, abortController);
|
|
98
|
+
|
|
99
|
+
this.runSubagent(taskId, options.task, displayLabel, options.origin)
|
|
100
|
+
.catch((error) => {
|
|
101
|
+
logger.error({ error, taskId }, 'Subagent failed');
|
|
102
|
+
})
|
|
103
|
+
.finally(() => {
|
|
104
|
+
this.runningTasks.delete(taskId);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
logger.info({ taskId, label: displayLabel }, 'Spawned subagent');
|
|
108
|
+
return `子任务 [${displayLabel}] 已启动 (id: ${taskId}),完成后会自动通知你。`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getRunningCount(): number {
|
|
112
|
+
return this.runningTasks.size;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── 内部方法 ──────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
private async runSubagent(
|
|
118
|
+
taskId: string,
|
|
119
|
+
task: string,
|
|
120
|
+
label: string,
|
|
121
|
+
origin: SubagentOrigin,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
logger.info({ taskId, label }, 'Subagent starting task');
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const allTools = this.createTools();
|
|
127
|
+
let tools = allTools.filter(t => SUBAGENT_ALLOWED_TOOLS.has(t.name));
|
|
128
|
+
if (this.execPolicyConfig) {
|
|
129
|
+
tools = applyExecPolicyToTools(this.execPolicyConfig, tools);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const systemPrompt = this.buildSubagentPrompt(task);
|
|
133
|
+
const agent = createAgent(this.provider, {
|
|
134
|
+
systemPrompt,
|
|
135
|
+
tools,
|
|
136
|
+
maxIterations: this.maxIterations,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const result = await agent.run(task);
|
|
140
|
+
const finalResult = result.content || '任务已完成,但未生成最终响应。';
|
|
141
|
+
|
|
142
|
+
logger.info({ taskId }, 'Subagent completed successfully');
|
|
143
|
+
await this.announceResult(taskId, label, task, finalResult, origin, 'ok');
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const errorMsg = `Error: ${error}`;
|
|
146
|
+
logger.error({ taskId, error }, 'Subagent failed');
|
|
147
|
+
await this.announceResult(taskId, label, task, errorMsg, origin, 'error');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async announceResult(
|
|
152
|
+
taskId: string,
|
|
153
|
+
label: string,
|
|
154
|
+
task: string,
|
|
155
|
+
result: string,
|
|
156
|
+
origin: SubagentOrigin,
|
|
157
|
+
status: 'ok' | 'error',
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
if (!this.resultSender) {
|
|
160
|
+
logger.warn({ taskId }, 'No result sender configured, discarding subagent result');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const statusText = status === 'ok' ? '已完成' : '执行失败';
|
|
165
|
+
const announceContent = `[后台任务 '${label}' ${statusText}]\n\n任务: ${task}\n\n结果:\n${result}`;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await this.resultSender(origin, announceContent);
|
|
169
|
+
logger.debug({ taskId, origin }, 'Subagent announced result');
|
|
170
|
+
} catch (e) {
|
|
171
|
+
logger.error({ taskId, error: e }, 'Failed to announce subagent result');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private buildSubagentPrompt(task: string): string {
|
|
176
|
+
return `# 子任务 Agent
|
|
177
|
+
|
|
178
|
+
你是一个被主 agent 派生出来执行特定任务的子 agent。
|
|
179
|
+
|
|
180
|
+
## 你的任务
|
|
181
|
+
${task}
|
|
182
|
+
|
|
183
|
+
## 规则
|
|
184
|
+
1. 专注完成分配的任务,不做其他事情
|
|
185
|
+
2. 你的最终回复会被报告给主 agent,并转达给用户
|
|
186
|
+
3. 不要发起对话或承担额外任务
|
|
187
|
+
4. 回复要简洁但信息充分
|
|
188
|
+
|
|
189
|
+
## 你可以做的
|
|
190
|
+
- 读写工作区内的文件
|
|
191
|
+
- 执行 Shell 命令
|
|
192
|
+
- 搜索和抓取网页
|
|
193
|
+
- 彻底完成任务
|
|
194
|
+
|
|
195
|
+
## 你不能做的
|
|
196
|
+
- 直接向用户发送消息
|
|
197
|
+
- 派生其他子任务
|
|
198
|
+
- 访问主 agent 的对话历史
|
|
199
|
+
|
|
200
|
+
## 工作区
|
|
201
|
+
你的工作区路径: ${this.workspace}
|
|
202
|
+
|
|
203
|
+
完成任务后,请提供清晰的发现或操作摘要。`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
dispose(): void {
|
|
207
|
+
this.runningTasks.clear();
|
|
208
|
+
}
|
|
209
|
+
}
|
package/src/ai/types.ts
CHANGED
|
@@ -128,6 +128,14 @@ export interface ChatCompletionChunkChoice {
|
|
|
128
128
|
// Provider 类型
|
|
129
129
|
// ============================================================================
|
|
130
130
|
|
|
131
|
+
/** Provider 能力声明 */
|
|
132
|
+
export interface ProviderCapabilities {
|
|
133
|
+
vision?: boolean;
|
|
134
|
+
streaming?: boolean;
|
|
135
|
+
toolCalling?: boolean;
|
|
136
|
+
thinking?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
/** Provider 配置 */
|
|
132
140
|
export interface ProviderConfig {
|
|
133
141
|
apiKey?: string;
|
|
@@ -136,6 +144,10 @@ export interface ProviderConfig {
|
|
|
136
144
|
timeout?: number;
|
|
137
145
|
maxRetries?: number;
|
|
138
146
|
headers?: Record<string, string>;
|
|
147
|
+
/** 上下文窗口大小(token 数),各 Provider 映射到自身参数 */
|
|
148
|
+
contextWindow?: number;
|
|
149
|
+
/** Provider 能力声明 */
|
|
150
|
+
capabilities?: ProviderCapabilities;
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
/** Provider 接口 */
|
|
@@ -154,6 +166,12 @@ export interface AIProvider {
|
|
|
154
166
|
|
|
155
167
|
/** 检查连接 */
|
|
156
168
|
healthCheck?(): Promise<boolean>;
|
|
169
|
+
|
|
170
|
+
/** 上下文窗口大小(token 数),由 Provider 实现暴露 */
|
|
171
|
+
contextWindow?: number;
|
|
172
|
+
|
|
173
|
+
/** Provider 能力声明 */
|
|
174
|
+
capabilities?: ProviderCapabilities;
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
// ============================================================================
|
|
@@ -241,6 +259,13 @@ export interface Session {
|
|
|
241
259
|
// AI Service 配置
|
|
242
260
|
// ============================================================================
|
|
243
261
|
|
|
262
|
+
/** Ollama-specific fields for AIConfig typing (mirrors OllamaConfig) */
|
|
263
|
+
export interface OllamaProviderConfig extends ProviderConfig {
|
|
264
|
+
host?: string;
|
|
265
|
+
models?: string[];
|
|
266
|
+
num_ctx?: number;
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
/** AI 服务配置 */
|
|
245
270
|
export interface AIConfig {
|
|
246
271
|
enabled?: boolean;
|
|
@@ -251,7 +276,7 @@ export interface AIConfig {
|
|
|
251
276
|
deepseek?: ProviderConfig;
|
|
252
277
|
moonshot?: ProviderConfig;
|
|
253
278
|
zhipu?: ProviderConfig;
|
|
254
|
-
ollama?:
|
|
279
|
+
ollama?: OllamaProviderConfig;
|
|
255
280
|
custom?: ProviderConfig[];
|
|
256
281
|
};
|
|
257
282
|
sessions?: {
|
|
@@ -284,7 +309,9 @@ export interface AIConfig {
|
|
|
284
309
|
allowedTools?: string[];
|
|
285
310
|
/** bash 执行策略:deny=禁止执行,allowlist=仅允许列表内命令,full=不限制 */
|
|
286
311
|
execSecurity?: 'deny' | 'allowlist' | 'full';
|
|
287
|
-
/**
|
|
312
|
+
/** 预设命令白名单模式:readonly / network / development / custom(默认 custom,使用自定义 execAllowlist) */
|
|
313
|
+
execPreset?: 'readonly' | 'network' | 'development' | 'custom';
|
|
314
|
+
/** allowlist 模式下允许的命令(支持正则字符串,如 "^ls "、"^cat "),与 preset 合并 */
|
|
288
315
|
execAllowlist?: string[];
|
|
289
316
|
/** allowlist 未命中时:true=需审批(当前实现为拒绝并提示),false=直接拒绝 */
|
|
290
317
|
execAsk?: boolean;
|