@vibe-forge/core 0.7.5 → 0.8.3
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 +13 -72
- package/src/ws.ts +2 -12
- package/src/adapter/index.ts +0 -6
- package/src/adapter/loader.ts +0 -11
- package/src/adapter/type.ts +0 -117
- 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 -25
- package/src/controllers/task/index.ts +0 -2
- package/src/controllers/task/prepare.ts +0 -74
- package/src/controllers/task/run.ts +0 -231
- package/src/controllers/task/schema.ts +0 -131
- package/src/controllers/task/type.ts +0 -6
- package/src/hooks/bridge.ts +0 -368
- package/src/hooks/call.ts +0 -74
- package/src/hooks/index.ts +0 -41
- package/src/hooks/loader.ts +0 -79
- package/src/hooks/native.ts +0 -116
- package/src/hooks/runtime.ts +0 -139
- package/src/hooks/type.ts +0 -145
- 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
- package/src/utils/workspace-assets.ts +0 -919
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
|
|
5
|
-
import { execCommand, execShellCommand, linkPreparedNodeModules, pathExists } from './utils'
|
|
6
|
-
|
|
7
|
-
interface CategoryWorkspaceInput {
|
|
8
|
-
workspaceFolder?: string
|
|
9
|
-
category: string
|
|
10
|
-
baseCommit: string
|
|
11
|
-
setupCommand: string
|
|
12
|
-
timeoutSec: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CaseWorkspaceInput {
|
|
16
|
-
workspaceFolder?: string
|
|
17
|
-
category: string
|
|
18
|
-
title: string
|
|
19
|
-
runId: string
|
|
20
|
-
baseCommit: string
|
|
21
|
-
setupCommand: string
|
|
22
|
-
timeoutSec: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface CategoryWorkspaceState {
|
|
26
|
-
workspacePath: string
|
|
27
|
-
gitRoot: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface CaseWorkspaceState extends CategoryWorkspaceState {
|
|
31
|
-
caseWorkspacePath: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const categoryWorkspaceInflight = new Map<string, Promise<CategoryWorkspaceState>>()
|
|
35
|
-
|
|
36
|
-
const resolveWorktreeRoot = (workspaceFolder = process.cwd()) =>
|
|
37
|
-
resolve(workspaceFolder, '.ai/worktress/benchmark')
|
|
38
|
-
|
|
39
|
-
const findGitRoot = async (workspaceFolder: string) => {
|
|
40
|
-
const result = await execCommand({
|
|
41
|
-
command: 'git',
|
|
42
|
-
args: ['rev-parse', '--show-toplevel'],
|
|
43
|
-
cwd: workspaceFolder
|
|
44
|
-
})
|
|
45
|
-
if (result.exitCode !== 0) {
|
|
46
|
-
throw new Error(result.stderr || 'Failed to resolve git root')
|
|
47
|
-
}
|
|
48
|
-
return result.stdout.trim()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const detachWorktree = async (gitRoot: string, worktreePath: string) => {
|
|
52
|
-
const result = await execCommand({
|
|
53
|
-
command: 'git',
|
|
54
|
-
args: ['worktree', 'remove', '--force', worktreePath],
|
|
55
|
-
cwd: gitRoot
|
|
56
|
-
})
|
|
57
|
-
const output = `${result.stdout}\n${result.stderr}`
|
|
58
|
-
if (result.exitCode !== 0 && !output.includes('is not a working tree')) {
|
|
59
|
-
throw new Error(output.trim() || `Failed to remove worktree: ${worktreePath}`)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const ensureCategoryWorkspace = async (input: CategoryWorkspaceInput): Promise<CategoryWorkspaceState> => {
|
|
64
|
-
const workspaceFolder = input.workspaceFolder ?? process.cwd()
|
|
65
|
-
const lockKey = `${workspaceFolder}:${input.category}`
|
|
66
|
-
const inflight = categoryWorkspaceInflight.get(lockKey)
|
|
67
|
-
if (inflight != null) {
|
|
68
|
-
return inflight
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const promise = (async () => {
|
|
72
|
-
const gitRoot = await findGitRoot(workspaceFolder)
|
|
73
|
-
const worktreeRoot = resolveWorktreeRoot(workspaceFolder)
|
|
74
|
-
const workspacePath = resolve(worktreeRoot, input.category)
|
|
75
|
-
const statePath = resolve(workspacePath, '.benchmark-state.json')
|
|
76
|
-
|
|
77
|
-
await mkdir(worktreeRoot, { recursive: true })
|
|
78
|
-
|
|
79
|
-
const currentState = await pathExists(statePath)
|
|
80
|
-
? JSON.parse(await readFile(statePath, 'utf-8')) as {
|
|
81
|
-
baseCommit?: string
|
|
82
|
-
setupCommand?: string
|
|
83
|
-
}
|
|
84
|
-
: null
|
|
85
|
-
|
|
86
|
-
const needsRecreate = !await pathExists(workspacePath) ||
|
|
87
|
-
currentState?.baseCommit !== input.baseCommit ||
|
|
88
|
-
currentState?.setupCommand !== input.setupCommand
|
|
89
|
-
|
|
90
|
-
if (needsRecreate) {
|
|
91
|
-
if (await pathExists(workspacePath)) {
|
|
92
|
-
await detachWorktree(gitRoot, workspacePath)
|
|
93
|
-
await rm(workspacePath, { force: true, recursive: true })
|
|
94
|
-
}
|
|
95
|
-
const addResult = await execCommand({
|
|
96
|
-
command: 'git',
|
|
97
|
-
args: ['worktree', 'add', '--force', '--detach', workspacePath, input.baseCommit],
|
|
98
|
-
cwd: gitRoot
|
|
99
|
-
})
|
|
100
|
-
if (addResult.exitCode !== 0) {
|
|
101
|
-
throw new Error(addResult.stderr || `Failed to create worktree for ${input.category}`)
|
|
102
|
-
}
|
|
103
|
-
if (input.setupCommand.trim() !== '') {
|
|
104
|
-
const setupResult = await execShellCommand({
|
|
105
|
-
command: input.setupCommand,
|
|
106
|
-
cwd: workspacePath,
|
|
107
|
-
timeoutMs: input.timeoutSec * 1000
|
|
108
|
-
})
|
|
109
|
-
if (setupResult.exitCode !== 0) {
|
|
110
|
-
throw new Error(setupResult.stderr || setupResult.stdout || 'Failed to prepare category workspace')
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
await writeFile(statePath, `${JSON.stringify({
|
|
114
|
-
baseCommit: input.baseCommit,
|
|
115
|
-
setupCommand: input.setupCommand
|
|
116
|
-
}, null, 2)}\n`, 'utf-8')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
workspacePath,
|
|
121
|
-
gitRoot
|
|
122
|
-
}
|
|
123
|
-
})()
|
|
124
|
-
|
|
125
|
-
categoryWorkspaceInflight.set(lockKey, promise)
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
return await promise
|
|
129
|
-
} finally {
|
|
130
|
-
categoryWorkspaceInflight.delete(lockKey)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export const createCaseWorkspace = async (input: CaseWorkspaceInput): Promise<CaseWorkspaceState> => {
|
|
135
|
-
const categoryWorkspace = await ensureCategoryWorkspace(input)
|
|
136
|
-
const worktreeRoot = resolveWorktreeRoot(input.workspaceFolder ?? process.cwd())
|
|
137
|
-
const caseRoot = resolve(worktreeRoot, '.cases', input.category)
|
|
138
|
-
const caseWorkspacePath = resolve(caseRoot, `${input.title}-${input.runId}`)
|
|
139
|
-
|
|
140
|
-
await mkdir(caseRoot, { recursive: true })
|
|
141
|
-
if (await pathExists(caseWorkspacePath)) {
|
|
142
|
-
await detachWorktree(categoryWorkspace.gitRoot, caseWorkspacePath)
|
|
143
|
-
await rm(caseWorkspacePath, { force: true, recursive: true })
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const addResult = await execCommand({
|
|
147
|
-
command: 'git',
|
|
148
|
-
args: ['worktree', 'add', '--force', '--detach', caseWorkspacePath, input.baseCommit],
|
|
149
|
-
cwd: categoryWorkspace.gitRoot
|
|
150
|
-
})
|
|
151
|
-
if (addResult.exitCode !== 0) {
|
|
152
|
-
throw new Error(addResult.stderr || `Failed to create case workspace for ${input.category}/${input.title}`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
await linkPreparedNodeModules(categoryWorkspace.workspacePath, caseWorkspacePath)
|
|
156
|
-
|
|
157
|
-
if (input.setupCommand.trim() !== '') {
|
|
158
|
-
const setupResult = await execShellCommand({
|
|
159
|
-
command: input.setupCommand,
|
|
160
|
-
cwd: caseWorkspacePath,
|
|
161
|
-
timeoutMs: input.timeoutSec * 1000
|
|
162
|
-
})
|
|
163
|
-
if (setupResult.exitCode !== 0) {
|
|
164
|
-
throw new Error(setupResult.stderr || setupResult.stdout || 'Failed to prepare case workspace')
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
...categoryWorkspace,
|
|
170
|
-
caseWorkspacePath
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export const disposeCaseWorkspace = async (state: CaseWorkspaceState) => {
|
|
175
|
-
await detachWorktree(state.gitRoot, state.caseWorkspacePath)
|
|
176
|
-
if (await pathExists(state.caseWorkspacePath)) {
|
|
177
|
-
await rm(state.caseWorkspacePath, { force: true, recursive: true })
|
|
178
|
-
}
|
|
179
|
-
}
|
|
@@ -1,214 +0,0 @@
|
|
|
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 'channels': {
|
|
138
|
-
updateField(
|
|
139
|
-
'channels',
|
|
140
|
-
mergeMaskedValues(sectionValue, config.channels) as Config['channels']
|
|
141
|
-
)
|
|
142
|
-
return nextConfig
|
|
143
|
-
}
|
|
144
|
-
case 'adapters': {
|
|
145
|
-
updateField('adapters', mergeMaskedValues(sectionValue, config.adapters) as Config['adapters'])
|
|
146
|
-
return nextConfig
|
|
147
|
-
}
|
|
148
|
-
case 'plugins': {
|
|
149
|
-
updateField('plugins', sectionValue.plugins as Config['plugins'])
|
|
150
|
-
updateField(
|
|
151
|
-
'enabledPlugins',
|
|
152
|
-
mergeMaskedValues(sectionValue.enabledPlugins, config.enabledPlugins) as Config['enabledPlugins']
|
|
153
|
-
)
|
|
154
|
-
updateField(
|
|
155
|
-
'extraKnownMarketplaces',
|
|
156
|
-
mergeMaskedValues(
|
|
157
|
-
sectionValue.extraKnownMarketplaces,
|
|
158
|
-
config.extraKnownMarketplaces
|
|
159
|
-
) as Config['extraKnownMarketplaces']
|
|
160
|
-
)
|
|
161
|
-
return nextConfig
|
|
162
|
-
}
|
|
163
|
-
case 'mcp': {
|
|
164
|
-
updateField(
|
|
165
|
-
'mcpServers',
|
|
166
|
-
mergeMaskedValues(sectionValue.mcpServers, config.mcpServers) as Config['mcpServers']
|
|
167
|
-
)
|
|
168
|
-
updateField(
|
|
169
|
-
'defaultIncludeMcpServers',
|
|
170
|
-
sectionValue.defaultIncludeMcpServers as Config['defaultIncludeMcpServers']
|
|
171
|
-
)
|
|
172
|
-
updateField(
|
|
173
|
-
'defaultExcludeMcpServers',
|
|
174
|
-
sectionValue.defaultExcludeMcpServers as Config['defaultExcludeMcpServers']
|
|
175
|
-
)
|
|
176
|
-
updateField(
|
|
177
|
-
'noDefaultVibeForgeMcpServer',
|
|
178
|
-
sectionValue.noDefaultVibeForgeMcpServer as Config['noDefaultVibeForgeMcpServer']
|
|
179
|
-
)
|
|
180
|
-
return nextConfig
|
|
181
|
-
}
|
|
182
|
-
case 'shortcuts': {
|
|
183
|
-
updateField(
|
|
184
|
-
'shortcuts',
|
|
185
|
-
mergeMaskedValues(sectionValue, config.shortcuts) as Config['shortcuts']
|
|
186
|
-
)
|
|
187
|
-
return nextConfig
|
|
188
|
-
}
|
|
189
|
-
default:
|
|
190
|
-
return nextConfig
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export const updateConfigFile = async (options: UpdateConfigFileOptions) => {
|
|
195
|
-
const workspaceFolder = options.workspaceFolder ?? process.cwd()
|
|
196
|
-
const configPath = resolveConfigPath(workspaceFolder, options.source)
|
|
197
|
-
const format = extname(configPath).toLowerCase()
|
|
198
|
-
const hasExisting = existsSync(configPath)
|
|
199
|
-
const existingContent = hasExisting ? await readFile(configPath, 'utf-8') : ''
|
|
200
|
-
const existingConfig = hasExisting ? parseConfigContent(format, existingContent) : {}
|
|
201
|
-
const updatedConfig = updateConfigSection(existingConfig as Config, options.section, options.value)
|
|
202
|
-
await mkdir(dirname(configPath), { recursive: true })
|
|
203
|
-
await writeFile(
|
|
204
|
-
configPath,
|
|
205
|
-
serializeConfigContent(format, updatedConfig as Record<string, unknown>),
|
|
206
|
-
'utf-8'
|
|
207
|
-
)
|
|
208
|
-
resetConfigCache()
|
|
209
|
-
return { configPath, updatedConfig }
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export const configController = {
|
|
213
|
-
updateConfigFile
|
|
214
|
-
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,102 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import process from 'node:process'
|
|
2
|
-
|
|
3
|
-
import type { AdapterQueryOptions } from '#~/adapter/type.js'
|
|
4
|
-
import { resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/utils/workspace-assets.js'
|
|
5
|
-
|
|
6
|
-
export async function generateAdapterQueryOptions(
|
|
7
|
-
type: 'spec' | 'entity' | undefined,
|
|
8
|
-
name?: string,
|
|
9
|
-
cwd: string = process.cwd(),
|
|
10
|
-
input?: {
|
|
11
|
-
skills?: AdapterQueryOptions['skills']
|
|
12
|
-
}
|
|
13
|
-
) {
|
|
14
|
-
const bundle = await resolveWorkspaceAssetBundle({ cwd })
|
|
15
|
-
const [data, resolvedOptions] = await resolvePromptAssetSelection({
|
|
16
|
-
bundle,
|
|
17
|
-
type,
|
|
18
|
-
name,
|
|
19
|
-
input
|
|
20
|
-
})
|
|
21
|
-
return [
|
|
22
|
-
data,
|
|
23
|
-
resolvedOptions as Partial<AdapterQueryOptions>
|
|
24
|
-
] as const
|
|
25
|
-
}
|
|
@@ -1,74 +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
|
-
import { resolveWorkspaceAssetBundle } from '@vibe-forge/core/utils/workspace-assets'
|
|
9
|
-
|
|
10
|
-
import { resolveServerLogLevel } from '#~/env.js'
|
|
11
|
-
|
|
12
|
-
import type { RunTaskOptions } from './type'
|
|
13
|
-
|
|
14
|
-
export const prepare = async (
|
|
15
|
-
options: RunTaskOptions,
|
|
16
|
-
adapterOptions: AdapterQueryOptions
|
|
17
|
-
) => {
|
|
18
|
-
const cwd = options.cwd ?? process.env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? process.cwd()
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
sessionId = uuid()
|
|
22
|
-
} = adapterOptions
|
|
23
|
-
const {
|
|
24
|
-
ctxId = process.env.__VF_PROJECT_AI_CTX_ID__ ?? sessionId,
|
|
25
|
-
env: envFromOptions
|
|
26
|
-
} = options
|
|
27
|
-
const {
|
|
28
|
-
__IS_LOADER_CLI__: _0,
|
|
29
|
-
...prevEnv
|
|
30
|
-
} = {
|
|
31
|
-
...process.env,
|
|
32
|
-
...envFromOptions
|
|
33
|
-
}
|
|
34
|
-
const env: Record<string, string | null | undefined> = {
|
|
35
|
-
...prevEnv,
|
|
36
|
-
__VF_PROJECT_AI_CTX_ID__: ctxId,
|
|
37
|
-
__VF_PROJECT_AI_SESSION_ID__: sessionId,
|
|
38
|
-
__VF_PROJECT_AI_RUN_TYPE__: adapterOptions.runtime,
|
|
39
|
-
// 移除 NODE_OPTIONS 环境变量,防止干扰子进程的运行环境
|
|
40
|
-
NODE_OPTIONS: undefined
|
|
41
|
-
}
|
|
42
|
-
const logger = createLogger(
|
|
43
|
-
cwd,
|
|
44
|
-
ctxId,
|
|
45
|
-
sessionId,
|
|
46
|
-
env?.LOG_PREFIX ?? '',
|
|
47
|
-
resolveServerLogLevel(env)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const jsonVariables: Record<string, string | null | undefined> = {
|
|
51
|
-
...env,
|
|
52
|
-
WORKSPACE_FOLDER: cwd,
|
|
53
|
-
__VF_PROJECT_WORKSPACE_FOLDER__: cwd
|
|
54
|
-
}
|
|
55
|
-
const [config, userConfig] = await loadConfig({ jsonVariables })
|
|
56
|
-
const assets = await resolveWorkspaceAssetBundle({
|
|
57
|
-
cwd,
|
|
58
|
-
configs: [config, userConfig]
|
|
59
|
-
})
|
|
60
|
-
return [
|
|
61
|
-
{
|
|
62
|
-
ctxId,
|
|
63
|
-
cwd,
|
|
64
|
-
env,
|
|
65
|
-
cache: {
|
|
66
|
-
set: (key, value) => setCache(cwd, ctxId, sessionId, key, value),
|
|
67
|
-
get: (key) => getCache(cwd, ctxId, sessionId, key)
|
|
68
|
-
},
|
|
69
|
-
logger,
|
|
70
|
-
configs: [config, userConfig],
|
|
71
|
-
assets
|
|
72
|
-
} satisfies AdapterCtx
|
|
73
|
-
] as const
|
|
74
|
-
}
|