@vibe-forge/core 0.7.4 → 0.8.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.
- package/package.json +4 -46
- package/src/env.ts +5 -25
- package/src/index.ts +0 -5
- package/src/types.ts +12 -72
- package/src/ws.ts +3 -12
- package/src/adapter/index.ts +0 -6
- package/src/adapter/loader.ts +0 -11
- package/src/adapter/type.ts +0 -112
- package/src/config/load.ts +0 -122
- package/src/config/types.ts +0 -289
- package/src/config.ts +0 -2
- package/src/controllers/benchmark/discover.ts +0 -89
- package/src/controllers/benchmark/index.ts +0 -24
- package/src/controllers/benchmark/result-store.ts +0 -46
- package/src/controllers/benchmark/runner.ts +0 -415
- package/src/controllers/benchmark/schema.ts +0 -60
- package/src/controllers/benchmark/types.ts +0 -80
- package/src/controllers/benchmark/utils.ts +0 -144
- package/src/controllers/benchmark/workspace.ts +0 -179
- package/src/controllers/config/index.ts +0 -214
- package/src/controllers/system/assets/completed.mp3 +0 -0
- package/src/controllers/system/assets/mcp.png +0 -0
- package/src/controllers/system/index.ts +0 -102
- package/src/controllers/task/generate-adapter-query-options.ts +0 -218
- package/src/controllers/task/index.ts +0 -2
- package/src/controllers/task/prepare.ts +0 -68
- package/src/controllers/task/run.ts +0 -191
- package/src/controllers/task/schema.ts +0 -131
- package/src/controllers/task/type.ts +0 -6
- package/src/hooks/call.ts +0 -74
- package/src/hooks/index.ts +0 -39
- package/src/hooks/loader.ts +0 -75
- package/src/hooks/runtime.ts +0 -139
- package/src/hooks/type.ts +0 -120
- package/src/utils/cache.ts +0 -58
- package/src/utils/create-logger.ts +0 -89
- package/src/utils/definition-loader.ts +0 -530
- package/src/utils/filter.ts +0 -26
- package/src/utils/string-transform.ts +0 -37
- package/src/utils/uuid.ts +0 -6
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import process from 'node:process'
|
|
2
|
-
import { basename, dirname } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { AdapterQueryOptions } from '#~/adapter/type.js'
|
|
5
|
-
import { DefinitionLoader } from '#~/utils/definition-loader.js'
|
|
6
|
-
import type { Definition, Entity, Filter, Skill, Spec } from '#~/utils/definition-loader.js'
|
|
7
|
-
|
|
8
|
-
const filterSkills = (
|
|
9
|
-
skills: Definition<Skill>[],
|
|
10
|
-
selection?: AdapterQueryOptions['skills']
|
|
11
|
-
) => {
|
|
12
|
-
if (selection == null) return skills
|
|
13
|
-
|
|
14
|
-
const include = selection.include != null && selection.include.length > 0
|
|
15
|
-
? new Set(selection.include)
|
|
16
|
-
: undefined
|
|
17
|
-
const exclude = new Set(selection.exclude ?? [])
|
|
18
|
-
|
|
19
|
-
return skills.filter((skill) => {
|
|
20
|
-
const name = basename(dirname(skill.path))
|
|
21
|
-
return (include == null || include.has(name)) && !exclude.has(name)
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const dedupeSkills = (skills: Definition<Skill>[]) => {
|
|
26
|
-
const seen = new Set<string>()
|
|
27
|
-
return skills.filter((skill) => {
|
|
28
|
-
if (seen.has(skill.path)) return false
|
|
29
|
-
seen.add(skill.path)
|
|
30
|
-
return true
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type SkillSelectionInput =
|
|
35
|
-
| AdapterQueryOptions['skills']
|
|
36
|
-
| Entity['skills']
|
|
37
|
-
| Spec['skills']
|
|
38
|
-
|
|
39
|
-
const toNormalizedSkillSelection = (
|
|
40
|
-
selection?: SkillSelectionInput
|
|
41
|
-
): AdapterQueryOptions['skills'] | undefined => {
|
|
42
|
-
if (selection == null) return undefined
|
|
43
|
-
|
|
44
|
-
if (Array.isArray(selection)) {
|
|
45
|
-
return selection.length > 0
|
|
46
|
-
? {
|
|
47
|
-
include: selection
|
|
48
|
-
}
|
|
49
|
-
: undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if ('type' in selection && Array.isArray(selection.list)) {
|
|
53
|
-
const list = selection.list.filter((item): item is string => typeof item === 'string')
|
|
54
|
-
return selection.type === 'include'
|
|
55
|
-
? {
|
|
56
|
-
include: list
|
|
57
|
-
}
|
|
58
|
-
: {
|
|
59
|
-
exclude: list
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return selection
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const mergeSkillSelections = (
|
|
67
|
-
...selections: Array<SkillSelectionInput | undefined>
|
|
68
|
-
): AdapterQueryOptions['skills'] | undefined => {
|
|
69
|
-
let include: Set<string> | undefined
|
|
70
|
-
const exclude = new Set<string>()
|
|
71
|
-
|
|
72
|
-
for (const selection of selections) {
|
|
73
|
-
const normalized = toNormalizedSkillSelection(selection)
|
|
74
|
-
if (normalized == null) continue
|
|
75
|
-
|
|
76
|
-
if (normalized.include != null && normalized.include.length > 0) {
|
|
77
|
-
const current = new Set(normalized.include)
|
|
78
|
-
include = include == null
|
|
79
|
-
? current
|
|
80
|
-
: new Set([...include].filter(item => current.has(item)))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const item of normalized.exclude ?? []) {
|
|
84
|
-
exclude.add(item)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (include == null && exclude.size === 0) return undefined
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
include: include == null ? undefined : [...include],
|
|
92
|
-
exclude: exclude.size === 0 ? undefined : [...exclude]
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const getIncludedSkillNames = (selection?: SkillSelectionInput): string[] => {
|
|
97
|
-
const normalized = toNormalizedSkillSelection(selection)
|
|
98
|
-
return normalized?.include ?? []
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function generateAdapterQueryOptions(
|
|
102
|
-
type: 'spec' | 'entity' | undefined,
|
|
103
|
-
name?: string,
|
|
104
|
-
cwd: string = process.cwd(),
|
|
105
|
-
input?: {
|
|
106
|
-
skills?: AdapterQueryOptions['skills']
|
|
107
|
-
}
|
|
108
|
-
) {
|
|
109
|
-
const loader = new DefinitionLoader(cwd)
|
|
110
|
-
const options: Partial<AdapterQueryOptions> = {}
|
|
111
|
-
const systemPromptParts: string[] = []
|
|
112
|
-
let effectiveSkillSelection = toNormalizedSkillSelection(input?.skills)
|
|
113
|
-
|
|
114
|
-
// 1. 获取数据
|
|
115
|
-
// 1.1 获取默认数据
|
|
116
|
-
const entities = type !== 'entity'
|
|
117
|
-
? await loader.loadDefaultEntities()
|
|
118
|
-
: []
|
|
119
|
-
const defaultSkills = await loader.loadDefaultSkills()
|
|
120
|
-
const rules = await loader.loadDefaultRules()
|
|
121
|
-
const specs = await loader.loadDefaultSpecs()
|
|
122
|
-
|
|
123
|
-
// 1.2 获取指定数据
|
|
124
|
-
const targetSkills: Definition<Skill>[] = []
|
|
125
|
-
let targetBody = ''
|
|
126
|
-
let targetToolsFilter: Filter | undefined
|
|
127
|
-
let targetMcpServersFilter: Filter | undefined
|
|
128
|
-
if (type && name) {
|
|
129
|
-
const data = {
|
|
130
|
-
spec: await loader.loadSpec(name),
|
|
131
|
-
entity: await loader.loadEntity(name)
|
|
132
|
-
}[type]
|
|
133
|
-
if (!data) {
|
|
134
|
-
throw new Error(`Failed to load ${type} ${name}`)
|
|
135
|
-
}
|
|
136
|
-
const { attributes, body } = data
|
|
137
|
-
if (
|
|
138
|
-
attributes.rules
|
|
139
|
-
) {
|
|
140
|
-
// always load spec or entity tagged rules
|
|
141
|
-
rules.push(
|
|
142
|
-
...(
|
|
143
|
-
await loader.loadRules(attributes.rules, {
|
|
144
|
-
baseDir: dirname(data.path)
|
|
145
|
-
})
|
|
146
|
-
).map((rule) => ({
|
|
147
|
-
...rule,
|
|
148
|
-
attributes: {
|
|
149
|
-
...rule.attributes,
|
|
150
|
-
// 实体或流程中的规则为默认加载
|
|
151
|
-
always: true
|
|
152
|
-
}
|
|
153
|
-
}))
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
if (
|
|
157
|
-
attributes.skills
|
|
158
|
-
) {
|
|
159
|
-
effectiveSkillSelection = mergeSkillSelections(
|
|
160
|
-
input?.skills,
|
|
161
|
-
attributes.skills
|
|
162
|
-
)
|
|
163
|
-
targetSkills.push(
|
|
164
|
-
...filterSkills(
|
|
165
|
-
await loader.loadSkills(getIncludedSkillNames(attributes.skills)),
|
|
166
|
-
effectiveSkillSelection
|
|
167
|
-
)
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
targetBody = body
|
|
172
|
-
targetToolsFilter = attributes.tools
|
|
173
|
-
targetMcpServersFilter = attributes.mcpServers
|
|
174
|
-
}
|
|
175
|
-
const skills = filterSkills(defaultSkills, effectiveSkillSelection)
|
|
176
|
-
let selectedSkillsPrompt: Definition<Skill>[] = []
|
|
177
|
-
if (input?.skills?.include != null && input.skills.include.length > 0) {
|
|
178
|
-
selectedSkillsPrompt = dedupeSkills(
|
|
179
|
-
filterSkills(
|
|
180
|
-
await loader.loadSkills(input.skills.include),
|
|
181
|
-
effectiveSkillSelection
|
|
182
|
-
)
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 2. 基于数据生成上下文
|
|
187
|
-
// 2.1 加载关联上下文
|
|
188
|
-
systemPromptParts.push(loader.generateRulesPrompt(rules))
|
|
189
|
-
systemPromptParts.push(loader.generateSkillsPrompt(dedupeSkills(targetSkills)))
|
|
190
|
-
systemPromptParts.push(loader.generateSkillsPrompt(
|
|
191
|
-
selectedSkillsPrompt.filter(skill => !targetSkills.some(target => target.path === skill.path))
|
|
192
|
-
))
|
|
193
|
-
// 2.2 加载上下文路由
|
|
194
|
-
systemPromptParts.push(loader.generateEntitiesRoutePrompt(entities))
|
|
195
|
-
systemPromptParts.push(loader.generateSkillsRoutePrompt(skills))
|
|
196
|
-
systemPromptParts.push(loader.generateSpecRoutePrompt(specs))
|
|
197
|
-
// 2.3 加载目标上下文与配置
|
|
198
|
-
systemPromptParts.push(targetBody)
|
|
199
|
-
targetToolsFilter && (
|
|
200
|
-
options.tools = targetToolsFilter
|
|
201
|
-
)
|
|
202
|
-
targetMcpServersFilter && (
|
|
203
|
-
options.mcpServers = targetMcpServersFilter
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
options.systemPrompt = systemPromptParts.join('\n\n')
|
|
207
|
-
return [
|
|
208
|
-
{
|
|
209
|
-
rules,
|
|
210
|
-
targetSkills,
|
|
211
|
-
entities,
|
|
212
|
-
skills,
|
|
213
|
-
specs,
|
|
214
|
-
targetBody
|
|
215
|
-
},
|
|
216
|
-
options
|
|
217
|
-
] as const
|
|
218
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import process from 'node:process'
|
|
2
|
-
|
|
3
|
-
import { loadConfig } from '@vibe-forge/core'
|
|
4
|
-
import type { AdapterCtx, AdapterQueryOptions } from '@vibe-forge/core/adapter'
|
|
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 { resolveServerLogLevel } from '#~/env.js'
|
|
10
|
-
|
|
11
|
-
import type { RunTaskOptions } from './type'
|
|
12
|
-
|
|
13
|
-
export const prepare = async (
|
|
14
|
-
options: RunTaskOptions,
|
|
15
|
-
adapterOptions: AdapterQueryOptions
|
|
16
|
-
) => {
|
|
17
|
-
const cwd = options.cwd ?? process.env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? process.cwd()
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
sessionId = uuid()
|
|
21
|
-
} = adapterOptions
|
|
22
|
-
const {
|
|
23
|
-
ctxId = process.env.__VF_PROJECT_AI_CTX_ID__ ?? sessionId,
|
|
24
|
-
env: envFromOptions
|
|
25
|
-
} = options
|
|
26
|
-
const {
|
|
27
|
-
__IS_LOADER_CLI__: _0,
|
|
28
|
-
...prevEnv
|
|
29
|
-
} = {
|
|
30
|
-
...process.env,
|
|
31
|
-
...envFromOptions
|
|
32
|
-
}
|
|
33
|
-
const env: Record<string, string | null | undefined> = {
|
|
34
|
-
...prevEnv,
|
|
35
|
-
__VF_PROJECT_AI_CTX_ID__: ctxId,
|
|
36
|
-
__VF_PROJECT_AI_SESSION_ID__: sessionId,
|
|
37
|
-
__VF_PROJECT_AI_RUN_TYPE__: adapterOptions.runtime,
|
|
38
|
-
// 移除 NODE_OPTIONS 环境变量,防止干扰子进程的运行环境
|
|
39
|
-
NODE_OPTIONS: undefined
|
|
40
|
-
}
|
|
41
|
-
const logger = createLogger(
|
|
42
|
-
cwd,
|
|
43
|
-
ctxId,
|
|
44
|
-
sessionId,
|
|
45
|
-
env?.LOG_PREFIX ?? '',
|
|
46
|
-
resolveServerLogLevel(env)
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const jsonVariables: Record<string, string | null | undefined> = {
|
|
50
|
-
...env,
|
|
51
|
-
WORKSPACE_FOLDER: cwd,
|
|
52
|
-
__VF_PROJECT_WORKSPACE_FOLDER__: cwd
|
|
53
|
-
}
|
|
54
|
-
const [config, userConfig] = await loadConfig({ jsonVariables })
|
|
55
|
-
return [
|
|
56
|
-
{
|
|
57
|
-
ctxId,
|
|
58
|
-
cwd,
|
|
59
|
-
env,
|
|
60
|
-
cache: {
|
|
61
|
-
set: (key, value) => setCache(cwd, ctxId, sessionId, key, value),
|
|
62
|
-
get: (key) => getCache(cwd, ctxId, sessionId, key)
|
|
63
|
-
},
|
|
64
|
-
logger,
|
|
65
|
-
configs: [config, userConfig]
|
|
66
|
-
} satisfies AdapterCtx
|
|
67
|
-
] as const
|
|
68
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import type { AdapterCtx, AdapterOutputEvent, AdapterQueryOptions } from '#~/adapter/index.js'
|
|
2
|
-
import { loadAdapter } from '#~/adapter/index.js'
|
|
3
|
-
import type { ModelServiceConfig } from '#~/config.js'
|
|
4
|
-
import { callHook } from '#~/hooks/call.js'
|
|
5
|
-
import type { TaskDetail } from '#~/types.js'
|
|
6
|
-
|
|
7
|
-
import { prepare } from './prepare'
|
|
8
|
-
import type { RunTaskOptions } from './type'
|
|
9
|
-
|
|
10
|
-
const normalizeNonEmptyString = (value: unknown) => (
|
|
11
|
-
typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
const pickFirstNonEmptyString = (values: unknown[]) =>
|
|
15
|
-
values
|
|
16
|
-
.map(normalizeNonEmptyString)
|
|
17
|
-
.find((value): value is string => value != null)
|
|
18
|
-
|
|
19
|
-
const resolveQueryModel = (params: {
|
|
20
|
-
config: AdapterCtx['configs'][0]
|
|
21
|
-
userConfig: AdapterCtx['configs'][1]
|
|
22
|
-
inputModel?: string
|
|
23
|
-
}) => {
|
|
24
|
-
const inputModel = normalizeNonEmptyString(params.inputModel)
|
|
25
|
-
// User explicitly provided a model → pass through as-is.
|
|
26
|
-
// The adapter decides CCR vs native based on whether it contains ",".
|
|
27
|
-
if (inputModel != null) return inputModel
|
|
28
|
-
|
|
29
|
-
// No explicit model → auto-resolve from modelServices config.
|
|
30
|
-
// Produces "service,model" format when services are configured,
|
|
31
|
-
// which signals the adapter to route through CCR.
|
|
32
|
-
const mergedModelServices = {
|
|
33
|
-
...(params.config?.modelServices ?? {}),
|
|
34
|
-
...(params.userConfig?.modelServices ?? {})
|
|
35
|
-
}
|
|
36
|
-
const mergedDefaultModel = pickFirstNonEmptyString(
|
|
37
|
-
[
|
|
38
|
-
params.userConfig?.defaultModel,
|
|
39
|
-
params.config?.defaultModel
|
|
40
|
-
]
|
|
41
|
-
)
|
|
42
|
-
const mergedDefaultModelService = pickFirstNonEmptyString(
|
|
43
|
-
[
|
|
44
|
-
params.userConfig?.defaultModelService,
|
|
45
|
-
params.config?.defaultModelService
|
|
46
|
-
]
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const serviceEntries = Object.entries(mergedModelServices)
|
|
50
|
-
const modelToService = new Map<string, string>()
|
|
51
|
-
const availableModels: string[] = []
|
|
52
|
-
for (const [serviceKey, serviceValue] of serviceEntries) {
|
|
53
|
-
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
54
|
-
? serviceValue as ModelServiceConfig
|
|
55
|
-
: undefined
|
|
56
|
-
const models = Array.isArray(service?.models)
|
|
57
|
-
? service?.models.filter(item => typeof item === 'string' && item.trim() !== '')
|
|
58
|
-
: []
|
|
59
|
-
for (const model of models) {
|
|
60
|
-
if (!modelToService.has(model)) modelToService.set(model, serviceKey)
|
|
61
|
-
availableModels.push(model)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (availableModels.length === 0) return undefined
|
|
66
|
-
|
|
67
|
-
const resolveDefaultModel = () => {
|
|
68
|
-
if (mergedDefaultModel && modelToService.has(mergedDefaultModel)) return mergedDefaultModel
|
|
69
|
-
if (mergedDefaultModelService && mergedModelServices[mergedDefaultModelService]) {
|
|
70
|
-
const service = mergedModelServices[mergedDefaultModelService] as ModelServiceConfig | undefined
|
|
71
|
-
const models = Array.isArray(service?.models)
|
|
72
|
-
? service?.models.filter((item: unknown) => typeof item === 'string' && (item as string).trim() !== '')
|
|
73
|
-
: []
|
|
74
|
-
if (models.length > 0) return models[0]
|
|
75
|
-
}
|
|
76
|
-
return availableModels[0]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const resolvedModel = resolveDefaultModel()
|
|
80
|
-
if (!resolvedModel) return undefined
|
|
81
|
-
|
|
82
|
-
const resolvedService = modelToService.get(resolvedModel) ??
|
|
83
|
-
mergedDefaultModelService ??
|
|
84
|
-
serviceEntries[0]?.[0]
|
|
85
|
-
|
|
86
|
-
return resolvedService ? `${resolvedService},${resolvedModel}` : resolvedModel
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
declare module '@vibe-forge/core' {
|
|
90
|
-
interface Cache {
|
|
91
|
-
base: Omit<AdapterCtx, 'logger' | 'cache'>
|
|
92
|
-
detail: TaskDetail
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const run = async (
|
|
97
|
-
options: RunTaskOptions,
|
|
98
|
-
adapterOptions: AdapterQueryOptions
|
|
99
|
-
) => {
|
|
100
|
-
const [ctx] = await prepare(options, adapterOptions)
|
|
101
|
-
const {
|
|
102
|
-
configs: [config, userConfig]
|
|
103
|
-
} = ctx
|
|
104
|
-
|
|
105
|
-
const { logger, cache, ...base } = ctx
|
|
106
|
-
|
|
107
|
-
await cache.set('base', base)
|
|
108
|
-
|
|
109
|
-
const adapters = {
|
|
110
|
-
...config?.adapters,
|
|
111
|
-
...userConfig?.adapters
|
|
112
|
-
}
|
|
113
|
-
// dprint-ignore
|
|
114
|
-
const adapterType =
|
|
115
|
-
// 0. adapter from options
|
|
116
|
-
options.adapter ??
|
|
117
|
-
// 1. config default adapter
|
|
118
|
-
config?.defaultAdapter ??
|
|
119
|
-
// 2. user config default adapter
|
|
120
|
-
userConfig?.defaultAdapter ??
|
|
121
|
-
// 3. first adapter in config
|
|
122
|
-
(() => {
|
|
123
|
-
const adapterNames = Object.keys(adapters)
|
|
124
|
-
if (adapterNames.length === 0) {
|
|
125
|
-
throw new Error('No adapter found in config, please set adapters in config file')
|
|
126
|
-
}
|
|
127
|
-
return adapterNames[0]
|
|
128
|
-
})()
|
|
129
|
-
|
|
130
|
-
const originalOnEvent = adapterOptions.onEvent
|
|
131
|
-
const wrappedOnEvent = (event: AdapterOutputEvent) => {
|
|
132
|
-
if (event.type === 'init') {
|
|
133
|
-
originalOnEvent({
|
|
134
|
-
...event,
|
|
135
|
-
data: {
|
|
136
|
-
...event.data,
|
|
137
|
-
adapter: adapterType
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (event.type === 'exit') {
|
|
144
|
-
const { data } = event
|
|
145
|
-
|
|
146
|
-
void callHook('TaskStop', {
|
|
147
|
-
adapter: adapterType,
|
|
148
|
-
cwd: ctx.cwd,
|
|
149
|
-
sessionId: adapterOptions.sessionId,
|
|
150
|
-
|
|
151
|
-
options,
|
|
152
|
-
adapterOptions,
|
|
153
|
-
|
|
154
|
-
exitCode: data.exitCode,
|
|
155
|
-
stderr: data.stderr
|
|
156
|
-
}, ctx.env)
|
|
157
|
-
.catch((e) => {
|
|
158
|
-
logger.error('[Hook] TaskStop failed', e)
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
originalOnEvent(event)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const adapter = await loadAdapter(adapterType)
|
|
165
|
-
await adapter.init?.(ctx)
|
|
166
|
-
|
|
167
|
-
const resolvedModel = resolveQueryModel({
|
|
168
|
-
config,
|
|
169
|
-
userConfig,
|
|
170
|
-
inputModel: adapterOptions.model
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
await callHook('TaskStart', {
|
|
174
|
-
adapter: adapterType,
|
|
175
|
-
cwd: ctx.cwd,
|
|
176
|
-
sessionId: adapterOptions.sessionId,
|
|
177
|
-
|
|
178
|
-
options,
|
|
179
|
-
adapterOptions
|
|
180
|
-
}, ctx.env)
|
|
181
|
-
const session = await adapter.query(
|
|
182
|
-
ctx,
|
|
183
|
-
{
|
|
184
|
-
...adapterOptions,
|
|
185
|
-
model: resolvedModel,
|
|
186
|
-
onEvent: wrappedOnEvent
|
|
187
|
-
}
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
return { session, ctx }
|
|
191
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
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>
|
package/src/hooks/call.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
|
-
import { spawn } from 'node:child_process'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import process from 'node:process'
|
|
5
|
-
|
|
6
|
-
import type { HookInputs, HookOutputs } from './type'
|
|
7
|
-
|
|
8
|
-
export type HookEventName = keyof HookInputs
|
|
9
|
-
|
|
10
|
-
type HookInputPayload<K extends HookEventName> = Omit<HookInputs[K], 'hookEventName'>
|
|
11
|
-
|
|
12
|
-
const pickHookEnv = (env: Record<string, unknown>): Record<string, string> => {
|
|
13
|
-
const result: Record<string, string> = {}
|
|
14
|
-
for (const [key, value] of Object.entries(env)) {
|
|
15
|
-
if (typeof value === 'string') {
|
|
16
|
-
result[key] = value
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return result
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const resolveHookCliJs = () => {
|
|
23
|
-
try {
|
|
24
|
-
const pkgJsonPath = require.resolve('@vibe-forge/cli/package.json')
|
|
25
|
-
return path.resolve(path.dirname(pkgJsonPath), 'call-hook.js')
|
|
26
|
-
} catch (error) {
|
|
27
|
-
throw new Error('Failed to resolve @vibe-forge/cli hook entry', { cause: error })
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const callHook = async <K extends HookEventName>(
|
|
32
|
-
hookEventName: K,
|
|
33
|
-
input: HookInputPayload<K>,
|
|
34
|
-
env: Record<string, unknown> = process.env
|
|
35
|
-
): Promise<HookOutputs[K]> => {
|
|
36
|
-
const childEnv = pickHookEnv(env)
|
|
37
|
-
const child = spawn(process.execPath, [resolveHookCliJs()], {
|
|
38
|
-
cwd: typeof input.cwd === 'string' ? input.cwd : process.cwd(),
|
|
39
|
-
env: childEnv,
|
|
40
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const stdoutChunks: Buffer[] = []
|
|
44
|
-
const stderrChunks: Buffer[] = []
|
|
45
|
-
|
|
46
|
-
child.stdout.on('data', chunk => stdoutChunks.push(chunk))
|
|
47
|
-
child.stderr.on('data', chunk => stderrChunks.push(chunk))
|
|
48
|
-
|
|
49
|
-
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
50
|
-
child.once('error', reject)
|
|
51
|
-
child.once('close', code => resolve(code ?? 0))
|
|
52
|
-
child.stdin.end(JSON.stringify({
|
|
53
|
-
...input,
|
|
54
|
-
hookEventName
|
|
55
|
-
}))
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const stdout = Buffer.concat(stdoutChunks).toString('utf-8').trim()
|
|
59
|
-
const stderr = Buffer.concat(stderrChunks).toString('utf-8').trim()
|
|
60
|
-
|
|
61
|
-
if (exitCode !== 0) {
|
|
62
|
-
throw new Error(`Failed to call hook: process exited with code ${exitCode}${stderr ? ` - ${stderr}` : ''}`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (stdout === '') {
|
|
66
|
-
return { continue: true } as HookOutputs[K]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
return JSON.parse(stdout) as HookOutputs[K]
|
|
71
|
-
} catch (error) {
|
|
72
|
-
throw new Error(`Failed to parse hook output: ${stdout}${stderr ? `\nstderr: ${stderr}` : ''}`, { cause: error })
|
|
73
|
-
}
|
|
74
|
-
}
|