@vibe-forge/core 0.3.0 → 0.5.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 +29 -10
- package/src/adapter/type.ts +10 -1
- package/src/channel.ts +132 -0
- package/src/config/load.ts +122 -0
- package/src/config/types.ts +269 -0
- package/src/config.ts +2 -375
- package/src/controllers/benchmark/discover.ts +89 -0
- package/src/controllers/benchmark/index.ts +24 -0
- package/src/controllers/benchmark/result-store.ts +46 -0
- package/src/controllers/benchmark/runner.ts +415 -0
- package/src/controllers/benchmark/schema.ts +60 -0
- package/src/controllers/benchmark/types.ts +80 -0
- package/src/controllers/benchmark/utils.ts +144 -0
- package/src/controllers/benchmark/workspace.ts +179 -0
- package/src/controllers/config/index.ts +7 -0
- package/src/controllers/task/generate-adapter-query-options.ts +12 -2
- package/src/controllers/task/prepare.ts +1 -1
- package/src/controllers/task/run.ts +115 -35
- package/src/env.ts +9 -8
- package/src/hooks/type.ts +35 -88
- package/src/index.ts +2 -1
- package/src/tools.ts +46 -0
- package/src/types.ts +2 -1
- package/src/utils/api.ts +32 -0
- package/src/utils/definition-loader.ts +1 -1
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
}
|
|
@@ -134,6 +134,13 @@ const updateConfigSection = (config: Config, section: string, value: unknown): C
|
|
|
134
134
|
)
|
|
135
135
|
return nextConfig
|
|
136
136
|
}
|
|
137
|
+
case 'channels': {
|
|
138
|
+
updateField(
|
|
139
|
+
'channels',
|
|
140
|
+
mergeMaskedValues(sectionValue, config.channels) as Config['channels']
|
|
141
|
+
)
|
|
142
|
+
return nextConfig
|
|
143
|
+
}
|
|
137
144
|
case 'adapters': {
|
|
138
145
|
updateField('adapters', mergeMaskedValues(sectionValue, config.adapters) as Config['adapters'])
|
|
139
146
|
return nextConfig
|
|
@@ -8,7 +8,7 @@ export async function generateAdapterQueryOptions(
|
|
|
8
8
|
type: 'spec' | 'entity' | undefined,
|
|
9
9
|
name?: string,
|
|
10
10
|
cwd: string = process.cwd()
|
|
11
|
-
)
|
|
11
|
+
) {
|
|
12
12
|
const loader = new DefinitionLoader(cwd)
|
|
13
13
|
const options: Partial<AdapterQueryOptions> = {}
|
|
14
14
|
const systemPromptParts: string[] = []
|
|
@@ -82,5 +82,15 @@ export async function generateAdapterQueryOptions(
|
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
options.systemPrompt = systemPromptParts.join('\n\n')
|
|
85
|
-
return
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
rules,
|
|
88
|
+
targetSkills,
|
|
89
|
+
entities,
|
|
90
|
+
skills,
|
|
91
|
+
specs,
|
|
92
|
+
targetBody
|
|
93
|
+
},
|
|
94
|
+
options
|
|
95
|
+
] as const
|
|
86
96
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import type { AdapterCtx, AdapterQueryOptions } from '@vibe-forge/core'
|
|
4
3
|
import { loadConfig } from '@vibe-forge/core'
|
|
4
|
+
import type { AdapterCtx, AdapterQueryOptions } from '@vibe-forge/core/adapter'
|
|
5
5
|
import { getCache, setCache } from '@vibe-forge/core/utils/cache'
|
|
6
6
|
import { createLogger } from '@vibe-forge/core/utils/create-logger'
|
|
7
7
|
import { uuid } from '@vibe-forge/core/utils/uuid'
|
|
@@ -1,10 +1,91 @@
|
|
|
1
|
-
import type { AdapterCtx, AdapterOutputEvent, AdapterQueryOptions
|
|
2
|
-
import { loadAdapter } from '
|
|
3
|
-
import {
|
|
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 type { TaskDetail } from '#~/types.js'
|
|
5
|
+
import { callHook } from '#~/utils/api.js'
|
|
4
6
|
|
|
5
7
|
import { prepare } from './prepare'
|
|
6
8
|
import type { RunTaskOptions } from './type'
|
|
7
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
|
+
|
|
8
89
|
declare module '@vibe-forge/core' {
|
|
9
90
|
interface Cache {
|
|
10
91
|
base: Omit<AdapterCtx, 'logger' | 'cache'>
|
|
@@ -25,12 +106,6 @@ export const run = async (
|
|
|
25
106
|
|
|
26
107
|
await cache.set('base', base)
|
|
27
108
|
|
|
28
|
-
const startTime = Date.now()
|
|
29
|
-
logger.info('[Framework] Process start', {
|
|
30
|
-
...base,
|
|
31
|
-
adapterOptions,
|
|
32
|
-
startDateTime: new Date(startTime).toLocaleString()
|
|
33
|
-
})
|
|
34
109
|
const adapters = {
|
|
35
110
|
...config?.adapters,
|
|
36
111
|
...userConfig?.adapters
|
|
@@ -52,47 +127,52 @@ export const run = async (
|
|
|
52
127
|
return adapterNames[0]
|
|
53
128
|
})()
|
|
54
129
|
|
|
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
130
|
const originalOnEvent = adapterOptions.onEvent
|
|
73
131
|
const wrappedOnEvent = (event: AdapterOutputEvent) => {
|
|
74
132
|
if (event.type === 'exit') {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
133
|
+
const { data } = event
|
|
134
|
+
|
|
135
|
+
void callHook('TaskStop', {
|
|
136
|
+
adapter: adapterType,
|
|
137
|
+
cwd: ctx.cwd,
|
|
138
|
+
sessionId: adapterOptions.sessionId,
|
|
139
|
+
|
|
140
|
+
options,
|
|
141
|
+
adapterOptions,
|
|
142
|
+
|
|
143
|
+
exitCode: data.exitCode,
|
|
144
|
+
stderr: data.stderr
|
|
145
|
+
}, ctx.env)
|
|
146
|
+
.catch((e) => {
|
|
147
|
+
logger.error('[Hook] TaskStop failed', e)
|
|
148
|
+
})
|
|
79
149
|
}
|
|
80
150
|
originalOnEvent(event)
|
|
81
151
|
}
|
|
82
152
|
|
|
83
153
|
const adapter = await loadAdapter(adapterType)
|
|
154
|
+
const resolvedModel = resolveQueryModel({
|
|
155
|
+
config,
|
|
156
|
+
userConfig,
|
|
157
|
+
inputModel: adapterOptions.model
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
await callHook('TaskStart', {
|
|
161
|
+
adapter: adapterType,
|
|
162
|
+
cwd: ctx.cwd,
|
|
163
|
+
sessionId: adapterOptions.sessionId,
|
|
164
|
+
|
|
165
|
+
options,
|
|
166
|
+
adapterOptions
|
|
167
|
+
}, ctx.env)
|
|
84
168
|
const session = await adapter.query(
|
|
85
169
|
ctx,
|
|
86
170
|
{
|
|
87
171
|
...adapterOptions,
|
|
172
|
+
model: resolvedModel,
|
|
88
173
|
onEvent: wrappedOnEvent
|
|
89
174
|
}
|
|
90
175
|
)
|
|
91
176
|
|
|
92
|
-
if (session.pid) {
|
|
93
|
-
detail.pid = session.pid
|
|
94
|
-
await saveDetail(detail)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
177
|
return { session, ctx }
|
|
98
178
|
}
|
package/src/env.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface ServerEnv {
|
|
|
8
8
|
__VF_PROJECT_AI_SERVER_LOG_DIR__: string
|
|
9
9
|
__VF_PROJECT_AI_SERVER_LOG_LEVEL__: 'debug' | 'info' | 'warn' | 'error'
|
|
10
10
|
__VF_PROJECT_AI_SERVER_ALLOW_CORS__: boolean
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
__VF_PROJECT_AI_CLIENT_MODE__?: 'dev' | 'static'
|
|
12
|
+
__VF_PROJECT_AI_CLIENT_BASE__?: string
|
|
13
|
+
__VF_PROJECT_AI_CLIENT_DIST_PATH__?: string
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function loadEnv(): ServerEnv {
|
|
@@ -21,9 +22,9 @@ export function loadEnv(): ServerEnv {
|
|
|
21
22
|
__VF_PROJECT_AI_SERVER_LOG_DIR__ = '.logs',
|
|
22
23
|
__VF_PROJECT_AI_SERVER_LOG_LEVEL__ = 'info',
|
|
23
24
|
__VF_PROJECT_AI_SERVER_ALLOW_CORS__,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
__VF_PROJECT_AI_CLIENT_MODE__ = 'static',
|
|
26
|
+
__VF_PROJECT_AI_CLIENT_BASE__,
|
|
27
|
+
__VF_PROJECT_AI_CLIENT_DIST_PATH__
|
|
27
28
|
} = processEnv || {}
|
|
28
29
|
return {
|
|
29
30
|
__VF_PROJECT_AI_SERVER_HOST__,
|
|
@@ -36,8 +37,8 @@ export function loadEnv(): ServerEnv {
|
|
|
36
37
|
__VF_PROJECT_AI_SERVER_ALLOW_CORS__: __VF_PROJECT_AI_SERVER_ALLOW_CORS__ != null
|
|
37
38
|
? __VF_PROJECT_AI_SERVER_ALLOW_CORS__ === 'true'
|
|
38
39
|
: true,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
__VF_PROJECT_AI_CLIENT_MODE__: __VF_PROJECT_AI_CLIENT_MODE__ as ServerEnv['__VF_PROJECT_AI_CLIENT_MODE__'],
|
|
41
|
+
__VF_PROJECT_AI_CLIENT_BASE__,
|
|
42
|
+
__VF_PROJECT_AI_CLIENT_DIST_PATH__
|
|
42
43
|
}
|
|
43
44
|
}
|
package/src/hooks/type.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ToolInput, ToolOutput } from '../tools'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* https://docs.anthropic.com/en/docs/claude-code/hooks#hook-input
|
|
3
5
|
*/
|
|
@@ -5,96 +7,8 @@ export interface HookInputCore {
|
|
|
5
7
|
cwd: string
|
|
6
8
|
sessionId: string
|
|
7
9
|
hookEventName: keyof HookInputs
|
|
8
|
-
transcriptPath: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude
|
|
13
|
-
*/
|
|
14
|
-
export interface ToolInputs {
|
|
15
|
-
mcp__TmarAITools__notify: {
|
|
16
|
-
title: string
|
|
17
|
-
description: string
|
|
18
|
-
sound?: boolean
|
|
19
|
-
}
|
|
20
|
-
'mcp__TmarAITools__run-tasks': {
|
|
21
|
-
taskId: string
|
|
22
|
-
agents: number[]
|
|
23
|
-
}
|
|
24
|
-
Read: {
|
|
25
|
-
filePath: string
|
|
26
|
-
}
|
|
27
|
-
LS: {
|
|
28
|
-
path: string
|
|
29
|
-
}
|
|
30
|
-
Edit: {
|
|
31
|
-
filePath: string
|
|
32
|
-
newString: string
|
|
33
|
-
oldString: string
|
|
34
|
-
}
|
|
35
|
-
Write: {
|
|
36
|
-
filePath: string
|
|
37
|
-
content: string
|
|
38
|
-
}
|
|
39
|
-
Bash: {
|
|
40
|
-
command: string
|
|
41
|
-
description: string
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ToolOutputs {
|
|
46
|
-
mcp__TmarAITools__notify: {}
|
|
47
|
-
'mcp__TmarAITools__run-tasks': {}
|
|
48
|
-
Read: {
|
|
49
|
-
type: 'text' | (string & {})
|
|
50
|
-
file: {
|
|
51
|
-
filePath: string
|
|
52
|
-
content: string
|
|
53
|
-
numLines: number
|
|
54
|
-
startLine: number
|
|
55
|
-
totalLines: number
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
LS: string
|
|
59
|
-
Edit: {
|
|
60
|
-
filePath: string
|
|
61
|
-
newString: string
|
|
62
|
-
oldString: string
|
|
63
|
-
originalFile: string
|
|
64
|
-
}
|
|
65
|
-
Write: {
|
|
66
|
-
filePath: string
|
|
67
|
-
content: string
|
|
68
|
-
}
|
|
69
|
-
Bash: {
|
|
70
|
-
stdout: string
|
|
71
|
-
stderr: string
|
|
72
|
-
interrupted: boolean
|
|
73
|
-
isImage: boolean
|
|
74
|
-
}
|
|
75
10
|
}
|
|
76
11
|
|
|
77
|
-
// dprint-ignore
|
|
78
|
-
export type ToolInput = keyof ToolInputs extends infer Keys
|
|
79
|
-
? Keys extends infer Key extends keyof ToolInputs
|
|
80
|
-
? {
|
|
81
|
-
toolName: Key
|
|
82
|
-
toolInput: ToolInputs[Key]
|
|
83
|
-
}
|
|
84
|
-
: never
|
|
85
|
-
: never
|
|
86
|
-
|
|
87
|
-
// dprint-ignore
|
|
88
|
-
export type ToolOutput = keyof ToolOutputs extends infer Keys
|
|
89
|
-
? Keys extends infer Key extends keyof ToolOutputs
|
|
90
|
-
? {
|
|
91
|
-
toolName: Key
|
|
92
|
-
toolInput: ToolInputs[Key]
|
|
93
|
-
toolResponse?: ToolOutputs[Key]
|
|
94
|
-
}
|
|
95
|
-
: never
|
|
96
|
-
: never
|
|
97
|
-
|
|
98
12
|
export interface HookInputs {
|
|
99
13
|
/**
|
|
100
14
|
* https://docs.anthropic.com/en/docs/claude-code/hooks#pretooluse-input
|
|
@@ -113,6 +27,34 @@ export interface HookInputs {
|
|
|
113
27
|
SessionEnd: HookInputCore & {
|
|
114
28
|
reason: string
|
|
115
29
|
}
|
|
30
|
+
|
|
31
|
+
StartTasks: HookInputCore & {
|
|
32
|
+
tasks: Array<{
|
|
33
|
+
description: string
|
|
34
|
+
type: 'default' | 'spec' | 'entity'
|
|
35
|
+
name?: string
|
|
36
|
+
adapter?: string
|
|
37
|
+
background?: boolean
|
|
38
|
+
}>
|
|
39
|
+
}
|
|
40
|
+
GenerateSystemPrompt: HookInputCore & {
|
|
41
|
+
type?: 'spec' | 'entity'
|
|
42
|
+
name?: string
|
|
43
|
+
data?: unknown
|
|
44
|
+
}
|
|
45
|
+
TaskStart: HookInputCore & {
|
|
46
|
+
adapter?: string
|
|
47
|
+
options: unknown
|
|
48
|
+
adapterOptions: unknown
|
|
49
|
+
}
|
|
50
|
+
TaskStop: HookInputCore & {
|
|
51
|
+
exitCode?: number
|
|
52
|
+
stderr?: string
|
|
53
|
+
adapter?: string
|
|
54
|
+
|
|
55
|
+
options: unknown
|
|
56
|
+
adapterOptions: unknown
|
|
57
|
+
}
|
|
116
58
|
}
|
|
117
59
|
|
|
118
60
|
export type HookInput = HookInputs[keyof HookInputs]
|
|
@@ -168,6 +110,11 @@ export interface HookOutputs {
|
|
|
168
110
|
SessionEnd: HookOutputCore
|
|
169
111
|
SubagentStop: HookOutputCore
|
|
170
112
|
PreCompact: HookOutputCore
|
|
113
|
+
|
|
114
|
+
StartTasks: HookOutputCore
|
|
115
|
+
GenerateSystemPrompt: HookOutputCore
|
|
116
|
+
TaskStart: HookOutputCore
|
|
117
|
+
TaskStop: HookOutputCore
|
|
171
118
|
}
|
|
172
119
|
|
|
173
120
|
export type HookOutput = HookOutputs[keyof HookOutputs]
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export * from './adapter'
|
|
2
1
|
export * from './config'
|
|
2
|
+
export * from './controllers/benchmark'
|
|
3
3
|
export * from './controllers/config'
|
|
4
4
|
export * from './controllers/system'
|
|
5
5
|
export * from './env'
|
|
6
6
|
export * from './hooks'
|
|
7
7
|
export * from './schema'
|
|
8
|
+
export * from './tools'
|
|
8
9
|
export * from './types'
|
|
9
10
|
export * from './ws'
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface StopTaskToolInput {
|
|
2
|
+
task_id?: string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface StartTasksToolInput {
|
|
6
|
+
tasks: Array<{
|
|
7
|
+
description?: string
|
|
8
|
+
type?: 'default' | 'spec' | 'entity'
|
|
9
|
+
name?: string
|
|
10
|
+
adapter?: string
|
|
11
|
+
permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
|
|
12
|
+
background?: boolean
|
|
13
|
+
}>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GetTaskInfoToolInput {
|
|
17
|
+
taskId: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ListTasksToolInput {}
|
|
21
|
+
|
|
22
|
+
export interface ToolInputs {
|
|
23
|
+
StartTasks: StartTasksToolInput
|
|
24
|
+
GetTaskInfo: GetTaskInfoToolInput
|
|
25
|
+
ListTasks: ListTasksToolInput
|
|
26
|
+
StopTask: StopTaskToolInput
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ToolOutputs {}
|
|
30
|
+
|
|
31
|
+
export type ToolName = keyof ToolInputs
|
|
32
|
+
|
|
33
|
+
export type ToolInput = keyof ToolInputs extends infer Keys ? Keys extends infer Key extends keyof ToolInputs ? {
|
|
34
|
+
toolName: Key
|
|
35
|
+
toolInput: ToolInputs[Key]
|
|
36
|
+
}
|
|
37
|
+
: never
|
|
38
|
+
: never
|
|
39
|
+
|
|
40
|
+
export type ToolOutput = keyof ToolOutputs extends infer Keys ? Keys extends infer Key extends keyof ToolOutputs ? {
|
|
41
|
+
toolName: Key
|
|
42
|
+
toolInput: ToolInputs[Key]
|
|
43
|
+
toolResponse?: ToolOutputs[Key]
|
|
44
|
+
}
|
|
45
|
+
: never
|
|
46
|
+
: never
|
package/src/types.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface Session {
|
|
|
25
25
|
|
|
26
26
|
export type ChatMessageContent =
|
|
27
27
|
| { type: 'text'; text: string }
|
|
28
|
+
| { type: 'image'; url: string; name?: string; size?: number; mimeType?: string }
|
|
28
29
|
| { type: 'tool_use'; id: string; name: string; input: any }
|
|
29
30
|
| { type: 'tool_result'; tool_use_id: string; content: any; is_error?: boolean }
|
|
30
31
|
|
|
@@ -60,7 +61,7 @@ export interface TaskDetail {
|
|
|
60
61
|
startTime: number
|
|
61
62
|
endTime?: number
|
|
62
63
|
description?: string
|
|
63
|
-
|
|
64
|
+
adapterType?: string
|
|
64
65
|
model?: string
|
|
65
66
|
exitCode?: number
|
|
66
67
|
}
|