@vibe-forge/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,207 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { dirname, extname, resolve } from 'node:path'
4
+ import process from 'node:process'
5
+
6
+ import { dump, load } from 'js-yaml'
7
+
8
+ import type { Config } from '../../config'
9
+ import { resetConfigCache } from '../../config'
10
+
11
+ export type ConfigSource = 'project' | 'user'
12
+
13
+ export interface UpdateConfigFileOptions {
14
+ workspaceFolder?: string
15
+ source: ConfigSource
16
+ section: string
17
+ value: unknown
18
+ }
19
+
20
+ const shouldMaskKey = (key: string) => /key|token|secret|password/i.test(key)
21
+
22
+ const projectConfigPaths = [
23
+ './.ai.config.json',
24
+ './infra/.ai.config.json',
25
+ './.ai.config.yaml',
26
+ './.ai.config.yml',
27
+ './infra/.ai.config.yaml',
28
+ './infra/.ai.config.yml'
29
+ ]
30
+
31
+ const userConfigPaths = [
32
+ './.ai.dev.config.json',
33
+ './infra/.ai.dev.config.json',
34
+ './.ai.dev.config.yaml',
35
+ './.ai.dev.config.yml',
36
+ './infra/.ai.dev.config.yaml',
37
+ './infra/.ai.dev.config.yml'
38
+ ]
39
+
40
+ const resolveConfigPath = (workspaceFolder: string, source: ConfigSource) => {
41
+ const paths = source === 'project' ? projectConfigPaths : userConfigPaths
42
+ for (const path of paths) {
43
+ const resolved = resolve(workspaceFolder, path)
44
+ if (existsSync(resolved)) {
45
+ return resolved
46
+ }
47
+ }
48
+ return resolve(workspaceFolder, paths[0])
49
+ }
50
+
51
+ const parseConfigContent = (format: string, content: string) => {
52
+ if (format === '.yaml' || format === '.yml') {
53
+ return (load(content) ?? {}) as Record<string, unknown>
54
+ }
55
+ return JSON.parse(content) as Record<string, unknown>
56
+ }
57
+
58
+ const serializeConfigContent = (format: string, value: Record<string, unknown>) => {
59
+ if (format === '.yaml' || format === '.yml') {
60
+ return `${dump(value, { noRefs: true, lineWidth: 120 })}\n`
61
+ }
62
+ return `${JSON.stringify(value, null, 2)}\n`
63
+ }
64
+
65
+ const mergeMaskedValues = (incoming: unknown, existing: unknown): unknown => {
66
+ if (Array.isArray(incoming)) return incoming
67
+ if (incoming != null && typeof incoming === 'object') {
68
+ const incomingRecord = incoming as Record<string, unknown>
69
+ const existingRecord = (existing != null && typeof existing === 'object')
70
+ ? (existing as Record<string, unknown>)
71
+ : {}
72
+ return Object.entries(incomingRecord).reduce<Record<string, unknown>>((acc, [key, val]) => {
73
+ if (shouldMaskKey(key) && val === '******') {
74
+ acc[key] = existingRecord[key]
75
+ } else {
76
+ acc[key] = mergeMaskedValues(val, existingRecord[key])
77
+ }
78
+ return acc
79
+ }, {})
80
+ }
81
+ return incoming
82
+ }
83
+
84
+ const updateConfigSection = (config: Config, section: string, value: unknown): Config => {
85
+ const nextConfig: Config = { ...config }
86
+ const sectionValue = (value != null && typeof value === 'object')
87
+ ? (value as Record<string, unknown>)
88
+ : {}
89
+
90
+ const updateField = <T extends keyof Config>(key: T, nextValue: Config[T] | undefined) => {
91
+ if (nextValue === undefined) {
92
+ delete nextConfig[key]
93
+ } else {
94
+ nextConfig[key] = nextValue
95
+ }
96
+ }
97
+
98
+ switch (section) {
99
+ case 'general': {
100
+ updateField('baseDir', sectionValue.baseDir as Config['baseDir'])
101
+ updateField('defaultAdapter', sectionValue.defaultAdapter as Config['defaultAdapter'])
102
+ updateField('defaultModelService', sectionValue.defaultModelService as Config['defaultModelService'])
103
+ updateField('defaultModel', sectionValue.defaultModel as Config['defaultModel'])
104
+ updateField('recommendedModels', sectionValue.recommendedModels as Config['recommendedModels'])
105
+ updateField('interfaceLanguage', sectionValue.interfaceLanguage as Config['interfaceLanguage'])
106
+ updateField('modelLanguage', sectionValue.modelLanguage as Config['modelLanguage'])
107
+ updateField('announcements', sectionValue.announcements as Config['announcements'])
108
+ updateField(
109
+ 'permissions',
110
+ mergeMaskedValues(sectionValue.permissions, config.permissions) as Config['permissions']
111
+ )
112
+ updateField(
113
+ 'env',
114
+ mergeMaskedValues(sectionValue.env, config.env) as Config['env']
115
+ )
116
+ updateField(
117
+ 'notifications',
118
+ mergeMaskedValues(sectionValue.notifications, config.notifications) as Config['notifications']
119
+ )
120
+ updateField(
121
+ 'shortcuts',
122
+ mergeMaskedValues(sectionValue.shortcuts, config.shortcuts) as Config['shortcuts']
123
+ )
124
+ return nextConfig
125
+ }
126
+ case 'conversation': {
127
+ updateField('conversation', mergeMaskedValues(sectionValue, config.conversation) as Config['conversation'])
128
+ return nextConfig
129
+ }
130
+ case 'modelServices': {
131
+ updateField(
132
+ 'modelServices',
133
+ mergeMaskedValues(sectionValue, config.modelServices) as Config['modelServices']
134
+ )
135
+ return nextConfig
136
+ }
137
+ case 'adapters': {
138
+ updateField('adapters', mergeMaskedValues(sectionValue, config.adapters) as Config['adapters'])
139
+ return nextConfig
140
+ }
141
+ case 'plugins': {
142
+ updateField('plugins', sectionValue.plugins as Config['plugins'])
143
+ updateField(
144
+ 'enabledPlugins',
145
+ mergeMaskedValues(sectionValue.enabledPlugins, config.enabledPlugins) as Config['enabledPlugins']
146
+ )
147
+ updateField(
148
+ 'extraKnownMarketplaces',
149
+ mergeMaskedValues(
150
+ sectionValue.extraKnownMarketplaces,
151
+ config.extraKnownMarketplaces
152
+ ) as Config['extraKnownMarketplaces']
153
+ )
154
+ return nextConfig
155
+ }
156
+ case 'mcp': {
157
+ updateField(
158
+ 'mcpServers',
159
+ mergeMaskedValues(sectionValue.mcpServers, config.mcpServers) as Config['mcpServers']
160
+ )
161
+ updateField(
162
+ 'defaultIncludeMcpServers',
163
+ sectionValue.defaultIncludeMcpServers as Config['defaultIncludeMcpServers']
164
+ )
165
+ updateField(
166
+ 'defaultExcludeMcpServers',
167
+ sectionValue.defaultExcludeMcpServers as Config['defaultExcludeMcpServers']
168
+ )
169
+ updateField(
170
+ 'noDefaultVibeForgeMcpServer',
171
+ sectionValue.noDefaultVibeForgeMcpServer as Config['noDefaultVibeForgeMcpServer']
172
+ )
173
+ return nextConfig
174
+ }
175
+ case 'shortcuts': {
176
+ updateField(
177
+ 'shortcuts',
178
+ mergeMaskedValues(sectionValue, config.shortcuts) as Config['shortcuts']
179
+ )
180
+ return nextConfig
181
+ }
182
+ default:
183
+ return nextConfig
184
+ }
185
+ }
186
+
187
+ export const updateConfigFile = async (options: UpdateConfigFileOptions) => {
188
+ const workspaceFolder = options.workspaceFolder ?? process.cwd()
189
+ const configPath = resolveConfigPath(workspaceFolder, options.source)
190
+ const format = extname(configPath).toLowerCase()
191
+ const hasExisting = existsSync(configPath)
192
+ const existingContent = hasExisting ? await readFile(configPath, 'utf-8') : ''
193
+ const existingConfig = hasExisting ? parseConfigContent(format, existingContent) : {}
194
+ const updatedConfig = updateConfigSection(existingConfig as Config, options.section, options.value)
195
+ await mkdir(dirname(configPath), { recursive: true })
196
+ await writeFile(
197
+ configPath,
198
+ serializeConfigContent(format, updatedConfig as Record<string, unknown>),
199
+ 'utf-8'
200
+ )
201
+ resetConfigCache()
202
+ return { configPath, updatedConfig }
203
+ }
204
+
205
+ export const configController = {
206
+ updateConfigFile
207
+ }
@@ -0,0 +1,102 @@
1
+ import { spawn } from 'node:child_process'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import type { NotificationMetadata } from 'node-notifier'
6
+ import notifier from 'node-notifier'
7
+ import z from 'zod'
8
+
9
+ const notifyOptionsSchema = z.object({
10
+ title: z.string().optional(),
11
+ description: z.string(),
12
+ icon: z.string().optional().describe('自定义图标路径'),
13
+ sound: z
14
+ .union([z.boolean(), z.string()])
15
+ .optional()
16
+ .describe('是否播放音效或指定音效文件路径'),
17
+ volume: z.number().optional().describe('音量,0-1 或 0-100'),
18
+ timeout: z
19
+ .union([z.number(), z.literal(false)])
20
+ .optional()
21
+ .describe('通知超时时间'),
22
+ actions: z.array(z.string()).optional().describe('通知操作按钮'),
23
+ needConfirm: z.boolean().optional().describe('是否需要用户确认')
24
+ })
25
+
26
+ type NotifyOptions = z.infer<typeof notifyOptionsSchema>
27
+
28
+ export const notify = async (options: NotifyOptions) => {
29
+ const {
30
+ title,
31
+ description,
32
+ icon,
33
+ sound = true,
34
+ volume,
35
+ timeout = 10 * 60 * 1000,
36
+ needConfirm
37
+ } = options
38
+
39
+ // 默认图标
40
+ const defaultIcon = path.resolve(__dirname, './assets/mcp.png')
41
+ // 默认音效
42
+ const defaultSound = path.resolve(__dirname, './assets/completed.mp3')
43
+
44
+ const resolvedSound = typeof sound === 'string'
45
+ ? sound
46
+ : (sound ? defaultSound : undefined)
47
+ const resolvedVolume = typeof volume === 'number'
48
+ ? (volume > 1 ? Math.min(volume, 100) / 100 : Math.max(volume, 0))
49
+ : undefined
50
+ const shouldPlaySound = resolvedSound != null && resolvedVolume !== 0
51
+ const shouldUseNotifierSound = !(resolvedVolume != null && resolvedSound != null && process.platform === 'darwin')
52
+ if (shouldPlaySound && !shouldUseNotifierSound && resolvedSound != null) {
53
+ try {
54
+ const args = ['-v', `${resolvedVolume ?? 1}`, resolvedSound]
55
+ const proc = spawn('afplay', args, { stdio: 'ignore', detached: true })
56
+ proc.unref()
57
+ } catch {
58
+ }
59
+ }
60
+
61
+ const [response, metadata] = await new Promise<
62
+ [string, NotificationMetadata | undefined]
63
+ >((ok, no) => {
64
+ notifier.notify(
65
+ {
66
+ icon: icon || defaultIcon,
67
+ title,
68
+ sound: shouldUseNotifierSound ? resolvedSound : undefined,
69
+ message: description,
70
+ wait: true,
71
+ reply: true,
72
+ timeout
73
+ },
74
+ (err, response, metadata) => {
75
+ if (err) {
76
+ no(err)
77
+ return
78
+ }
79
+ if (!needConfirm) {
80
+ return
81
+ }
82
+ ok([response, metadata])
83
+ }
84
+ )
85
+ if (!needConfirm) {
86
+ ok([
87
+ 'default',
88
+ {
89
+ activationType: 'default',
90
+ activationAt: Date.now().toLocaleString(),
91
+ deliveredAt: Date.now().toLocaleString()
92
+ }
93
+ ])
94
+ }
95
+ })
96
+ return { response, metadata }
97
+ }
98
+
99
+ export const systemController = {
100
+ notify,
101
+ notifyOptionsSchema
102
+ }
@@ -0,0 +1,86 @@
1
+ import process from 'node:process'
2
+
3
+ import type { AdapterQueryOptions } from '#~/adapter/type.js'
4
+ import { DefinitionLoader } from '#~/utils/definition-loader.js'
5
+ import type { Definition, Filter, Skill } from '#~/utils/definition-loader.js'
6
+
7
+ export async function generateAdapterQueryOptions(
8
+ type: 'spec' | 'entity' | undefined,
9
+ name?: string,
10
+ cwd: string = process.cwd()
11
+ ): Promise<Partial<AdapterQueryOptions>> {
12
+ const loader = new DefinitionLoader(cwd)
13
+ const options: Partial<AdapterQueryOptions> = {}
14
+ const systemPromptParts: string[] = []
15
+
16
+ // 1. 获取数据
17
+ // 1.1 获取默认数据
18
+ const entities = type !== 'entity'
19
+ ? await loader.loadDefaultEntities()
20
+ : []
21
+ const skills = await loader.loadDefaultSkills()
22
+ const rules = await loader.loadDefaultRules()
23
+ const specs = await loader.loadDefaultSpecs()
24
+
25
+ // 1.2 获取指定数据
26
+ const targetSkills: Definition<Skill>[] = []
27
+ let targetBody = ''
28
+ let targetToolsFilter: Filter | undefined
29
+ let targetMcpServersFilter: Filter | undefined
30
+ if (type && name) {
31
+ const data = {
32
+ spec: await loader.loadSpec(name),
33
+ entity: await loader.loadEntity(name)
34
+ }[type]
35
+ if (!data) {
36
+ throw new Error(`Failed to load ${type} ${name}`)
37
+ }
38
+ const { attributes, body } = data
39
+ if (
40
+ attributes.rules
41
+ ) {
42
+ // always load spec or entity tagged rules
43
+ rules.push(
44
+ ...(
45
+ await loader.loadRules(attributes.rules)
46
+ ).map((rule) => ({
47
+ ...rule,
48
+ attributes: {
49
+ ...rule.attributes,
50
+ // 实体或流程中的规则为默认加载
51
+ always: true
52
+ }
53
+ }))
54
+ )
55
+ }
56
+ if (
57
+ attributes.skills
58
+ ) {
59
+ targetSkills.push(...await loader.loadSkills(attributes.skills))
60
+ }
61
+
62
+ targetBody = body
63
+ targetToolsFilter = attributes.tools
64
+ targetMcpServersFilter = attributes.mcpServers
65
+ }
66
+
67
+ // 2. 基于数据生成上下文
68
+ // 2.1 加载关联上下文
69
+ systemPromptParts.push(loader.generateRulesPrompt(rules))
70
+ systemPromptParts.push(loader.generateSkillsPrompt(targetSkills))
71
+ // 2.2 加载上下文路由
72
+ systemPromptParts.push(loader.generateEntitiesRoutePrompt(entities))
73
+ systemPromptParts.push(loader.generateSkillsRoutePrompt(skills))
74
+ systemPromptParts.push(loader.generateSpecRoutePrompt(specs))
75
+ // 2.3 加载目标上下文与配置
76
+ systemPromptParts.push(targetBody)
77
+ targetToolsFilter && (
78
+ options.tools = targetToolsFilter
79
+ )
80
+ targetMcpServersFilter && (
81
+ options.mcpServers = targetMcpServersFilter
82
+ )
83
+
84
+ options.systemPrompt = systemPromptParts.join('\n\n')
85
+ return options
86
+ }
@@ -0,0 +1,2 @@
1
+ export { generateAdapterQueryOptions } from './generate-adapter-query-options'
2
+ export { run } from './run'
@@ -0,0 +1,60 @@
1
+ import process from 'node:process'
2
+
3
+ import type { AdapterCtx, AdapterQueryOptions } from '@vibe-forge/core'
4
+ import { loadConfig } from '@vibe-forge/core'
5
+ import { getCache, setCache } from '@vibe-forge/core/utils/cache'
6
+ import { createLogger } from '@vibe-forge/core/utils/create-logger'
7
+ import { uuid } from '@vibe-forge/core/utils/uuid'
8
+
9
+ import type { RunTaskOptions } from './type'
10
+
11
+ export const prepare = async (
12
+ options: RunTaskOptions,
13
+ adapterOptions: AdapterQueryOptions
14
+ ) => {
15
+ const cwd = options.cwd ?? process.env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? process.cwd()
16
+
17
+ const {
18
+ sessionId = uuid()
19
+ } = adapterOptions
20
+ const {
21
+ ctxId = process.env.__VF_PROJECT_AI_CTX_ID__ ?? sessionId,
22
+ env: envFromOptions
23
+ } = options
24
+ const {
25
+ __IS_LOADER_CLI__: _0,
26
+ ...prevEnv
27
+ } = {
28
+ ...process.env,
29
+ ...envFromOptions
30
+ }
31
+ const env: Record<string, string | null | undefined> = {
32
+ ...prevEnv,
33
+ __VF_PROJECT_AI_CTX_ID__: ctxId,
34
+ __VF_PROJECT_AI_SESSION_ID__: sessionId,
35
+ __VF_PROJECT_AI_RUN_TYPE__: adapterOptions.runtime,
36
+ // 移除 NODE_OPTIONS 环境变量,防止干扰子进程的运行环境
37
+ NODE_OPTIONS: undefined
38
+ }
39
+ const logger = createLogger(cwd, ctxId, sessionId, env?.LOG_PREFIX ?? '')
40
+
41
+ const jsonVariables: Record<string, string | null | undefined> = {
42
+ ...env,
43
+ WORKSPACE_FOLDER: cwd,
44
+ __VF_PROJECT_WORKSPACE_FOLDER__: cwd
45
+ }
46
+ const [config, userConfig] = await loadConfig({ jsonVariables })
47
+ return [
48
+ {
49
+ ctxId,
50
+ cwd,
51
+ env,
52
+ cache: {
53
+ set: (key, value) => setCache(cwd, ctxId, sessionId, key, value),
54
+ get: (key) => getCache(cwd, ctxId, sessionId, key)
55
+ },
56
+ logger,
57
+ configs: [config, userConfig]
58
+ } satisfies AdapterCtx
59
+ ] as const
60
+ }
@@ -0,0 +1,98 @@
1
+ import type { AdapterCtx, AdapterOutputEvent, AdapterQueryOptions, TaskDetail } from '@vibe-forge/core'
2
+ import { loadAdapter } from '@vibe-forge/core'
3
+ import { setCache } from '@vibe-forge/core/utils/cache'
4
+
5
+ import { prepare } from './prepare'
6
+ import type { RunTaskOptions } from './type'
7
+
8
+ declare module '@vibe-forge/core' {
9
+ interface Cache {
10
+ base: Omit<AdapterCtx, 'logger' | 'cache'>
11
+ detail: TaskDetail
12
+ }
13
+ }
14
+
15
+ export const run = async (
16
+ options: RunTaskOptions,
17
+ adapterOptions: AdapterQueryOptions
18
+ ) => {
19
+ const [ctx] = await prepare(options, adapterOptions)
20
+ const {
21
+ configs: [config, userConfig]
22
+ } = ctx
23
+
24
+ const { logger, cache, ...base } = ctx
25
+
26
+ await cache.set('base', base)
27
+
28
+ const startTime = Date.now()
29
+ logger.info('[Framework] Process start', {
30
+ ...base,
31
+ adapterOptions,
32
+ startDateTime: new Date(startTime).toLocaleString()
33
+ })
34
+ const adapters = {
35
+ ...config?.adapters,
36
+ ...userConfig?.adapters
37
+ }
38
+ // dprint-ignore
39
+ const adapterType =
40
+ // 0. adapter from options
41
+ options.adapter ??
42
+ // 1. config default adapter
43
+ config?.defaultAdapter ??
44
+ // 2. user config default adapter
45
+ userConfig?.defaultAdapter ??
46
+ // 3. first adapter in config
47
+ (() => {
48
+ const adapterNames = Object.keys(adapters)
49
+ if (adapterNames.length === 0) {
50
+ throw new Error('No adapter found in config, please set adapters in config file')
51
+ }
52
+ return adapterNames[0]
53
+ })()
54
+
55
+ const detail: TaskDetail = {
56
+ ctxId: ctx.ctxId,
57
+ sessionId: adapterOptions.sessionId,
58
+ status: 'running',
59
+ startTime,
60
+ description: adapterOptions.description,
61
+ adapter: adapterType,
62
+ model: adapterOptions.model
63
+ }
64
+
65
+ const saveDetail = async (d: TaskDetail) => {
66
+ // Save to caches/ctxId/detail.json (ignoring sessionId)
67
+ await setCache(ctx.cwd, ctx.ctxId, undefined, 'detail', d)
68
+ }
69
+
70
+ await saveDetail(detail)
71
+
72
+ const originalOnEvent = adapterOptions.onEvent
73
+ const wrappedOnEvent = (event: AdapterOutputEvent) => {
74
+ if (event.type === 'exit') {
75
+ detail.status = event.data.exitCode === 0 ? 'completed' : 'failed'
76
+ detail.endTime = Date.now()
77
+ detail.exitCode = event.data.exitCode ?? undefined
78
+ void saveDetail(detail).catch(console.error)
79
+ }
80
+ originalOnEvent(event)
81
+ }
82
+
83
+ const adapter = await loadAdapter(adapterType)
84
+ const session = await adapter.query(
85
+ ctx,
86
+ {
87
+ ...adapterOptions,
88
+ onEvent: wrappedOnEvent
89
+ }
90
+ )
91
+
92
+ if (session.pid) {
93
+ detail.pid = session.pid
94
+ await saveDetail(detail)
95
+ }
96
+
97
+ return { session, ctx }
98
+ }
@@ -0,0 +1,131 @@
1
+ import z from 'zod'
2
+
3
+ export const TaskOptions = z.object({
4
+ type: z
5
+ .union([
6
+ z.literal('entity'),
7
+ // 基础库 API 能力开发
8
+ z.literal('spec')
9
+ ])
10
+ .describe('任务模式'),
11
+ name: z.string().describe('垂类知识库基准目标'),
12
+ specParams: z.record(z.string()).describe('SPEC 执行时的参数').optional(),
13
+ runtime: z
14
+ .object({
15
+ type: z
16
+ .string()
17
+ .describe('选择特定的 AI CLI 类型'),
18
+ noDefaultSystemPrompt: z
19
+ .boolean()
20
+ .describe('是否禁用默认的系统提示')
21
+ .optional()
22
+ }),
23
+ description: z
24
+ .string()
25
+ .describe('本次任务的描述,介绍关于本次任务需要进行的工作')
26
+ .optional(),
27
+ sessionId: z
28
+ .string()
29
+ .describe(
30
+ '复用上次会话的历史消息作为本次任务的上下文。\n' +
31
+ '- 通常如果某个任务在执行的时候出现了非预期的错误,那么你可以通过传入相同的 sessionID 来继续这个会话\n' +
32
+ '- 如果有一个步骤需要间隔执行,比如说先执行任务 A 的 A-1,然后完成后执行 B 的 B-1,等 B-1 这个完成后回到 A-1 继续执行 A-2 时也可以使用'
33
+ )
34
+ .optional(),
35
+ frontendTimeout: z
36
+ .number()
37
+ .describe(
38
+ '前台等待时间的上限,单位秒,默认值为 8 分钟。' +
39
+ '在超过这个时间后该任务会被转化为一个后台任务,并直接返回当前的输出以及 sessionId,你可以通过 sessionId 来继续这个任务,或者查询任务的状态'
40
+ )
41
+ .default(8 * 60)
42
+ .optional(),
43
+ defaultModel: z.string().describe('默认的模型').optional()
44
+ })
45
+
46
+ export type TaskOptions = z.infer<typeof TaskOptions>
47
+
48
+ export const MCPRunTasksOptions = z.object({
49
+ tasks: z
50
+ .array(TaskOptions)
51
+ .describe(
52
+ '子任务列表。传递多个子任务时会并发执行多个,可用于优化整体任务效率。'
53
+ ),
54
+
55
+ bashDefaultTimeoutMs: z.number().optional(),
56
+ bashMaxTimeoutMs: z.number().optional(),
57
+ maxMcpOutputTokens: z.number().optional(),
58
+ mcpTimeout: z.number().optional(),
59
+ mcpToolTimeout: z.number().optional()
60
+ })
61
+
62
+ export type MCPRunTasksOptions = z.infer<typeof MCPRunTasksOptions>
63
+
64
+ export const Options = z
65
+ .object({
66
+ taskId: z
67
+ .string()
68
+ .describe(
69
+ '唯一 id,会用于关联多个任务相关信息。\n' +
70
+ '如果是第一次执行,则不需要传入,会在返回值中自动生成一个,在下次创建或复用的时候必须传入该值以供记录相关上下文。\n' +
71
+ '用户指定了 taskId,则以用户指定的 taskId 为准,在创建任务时则必须要指定对应的 taskId 参数。'
72
+ )
73
+ .optional()
74
+ })
75
+ .extend(MCPRunTasksOptions.shape)
76
+
77
+ export type Options = z.infer<typeof Options>
78
+
79
+ export const Entity = z.object({
80
+ prompt: z
81
+ .string()
82
+ .describe('实体的描述,简单介绍一下当前实体的作用。')
83
+ .optional(),
84
+ promptPath: z
85
+ .string()
86
+ .describe(
87
+ '实体的描述文件路径,文件内容为实体的描述。默认为当前目录下的 AGENTS.md 文件。'
88
+ )
89
+ .optional(),
90
+ rules: z
91
+ .array(
92
+ z.union([
93
+ z.string(),
94
+ z.discriminatedUnion('type', [
95
+ z.object({
96
+ type: z.literal('local').optional(),
97
+ path: z.string(),
98
+ desc: z.string().optional()
99
+ }),
100
+ z.object({
101
+ type: z.literal('remote'),
102
+ tags: z.array(z.string()).describe('关键 tag').optional(),
103
+ desc: z.string().describe('知识库的描述').optional()
104
+ })
105
+ ])
106
+ ])
107
+ )
108
+ .optional()
109
+ .describe('垂类 agent 的规则集合'),
110
+ skills: z
111
+ .object({
112
+ type: z.union([z.literal('include'), z.literal('exclude')]),
113
+ list: z.array(z.string()).describe('技能列表')
114
+ })
115
+ .optional(),
116
+ mcpServers: z
117
+ .object({
118
+ include: z.array(z.string()).describe('包含的服务名称列表'),
119
+ exclude: z.array(z.string()).describe('排除的服务名称列表')
120
+ })
121
+ .optional(),
122
+ tools: z
123
+ .object({
124
+ include: z.array(z.string()).describe('包含的工具名称列表'),
125
+ exclude: z.array(z.string()).describe('排除的工具名称列表')
126
+ })
127
+ .optional(),
128
+ defaultModel: z.string().optional()
129
+ })
130
+
131
+ export type Entity = z.infer<typeof Entity>
@@ -0,0 +1,6 @@
1
+ export interface RunTaskOptions {
2
+ ctxId?: string
3
+ adapter?: string
4
+ env?: Record<string, string | undefined | null>
5
+ cwd?: string
6
+ }