@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
package/src/hooks/index.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Writable } from 'node:stream'
|
|
2
|
-
|
|
3
|
-
import type { HookInputs, HookOutputs } from './type'
|
|
4
|
-
|
|
5
|
-
export interface HookContext {
|
|
6
|
-
logger: {
|
|
7
|
-
stream: Writable
|
|
8
|
-
info: (...args: unknown[]) => void
|
|
9
|
-
warn: (...args: unknown[]) => void
|
|
10
|
-
debug: (...args: unknown[]) => void
|
|
11
|
-
error: (...args: unknown[]) => void
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type Plugin =
|
|
16
|
-
& {
|
|
17
|
-
name?: string
|
|
18
|
-
}
|
|
19
|
-
& {
|
|
20
|
-
[P in keyof HookInputs]: (
|
|
21
|
-
ctx: HookContext,
|
|
22
|
-
input: HookInputs[P],
|
|
23
|
-
next: () => Promise<HookOutputs[P]>
|
|
24
|
-
) => Promise<HookOutputs[P]>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const definePlugin = (plugin: Partial<Plugin>) => plugin
|
|
28
|
-
|
|
29
|
-
export interface PluginMap {}
|
|
30
|
-
|
|
31
|
-
export type PluginConfig =
|
|
32
|
-
| (Partial<Plugin> | (() => Partial<Plugin>))[]
|
|
33
|
-
| Record<string, Record<string, unknown>>
|
|
34
|
-
| Partial<PluginMap>
|
|
35
|
-
|
|
36
|
-
export * from './call'
|
|
37
|
-
export * from './loader'
|
|
38
|
-
export * from './runtime'
|
|
39
|
-
export * from './type'
|
package/src/hooks/loader.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { Plugin, PluginConfig } from './index'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 解析单个插件配置
|
|
5
|
-
*/
|
|
6
|
-
const loadPlugin = async (
|
|
7
|
-
name: string,
|
|
8
|
-
config: Record<string, unknown>
|
|
9
|
-
): Promise<Partial<Plugin> | null> => {
|
|
10
|
-
try {
|
|
11
|
-
// eslint-disable-next-line ts/no-require-imports
|
|
12
|
-
const module = require(`${name}/hooks`)
|
|
13
|
-
|
|
14
|
-
// 兼容 ESM default export 和 CJS module.exports
|
|
15
|
-
const factory: (
|
|
16
|
-
// 直接导出插件对象
|
|
17
|
-
| Partial<Plugin>
|
|
18
|
-
// 导出工厂函数,接受配置并返回插件对象
|
|
19
|
-
| ((config: Record<string, unknown>) => Partial<Plugin>)
|
|
20
|
-
) = module.default ?? module
|
|
21
|
-
|
|
22
|
-
if (typeof factory === 'function') {
|
|
23
|
-
// TODO: 这里可以注入更多上下文,如全局配置、版本信息等
|
|
24
|
-
return factory(config)
|
|
25
|
-
} else if (typeof factory === 'object' && factory !== null) {
|
|
26
|
-
return factory
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.warn(`Plugin ${name} does not export a valid plugin factory or object.`)
|
|
30
|
-
return null
|
|
31
|
-
} catch (e) {
|
|
32
|
-
console.error(`Failed to load plugin ${name}:`, e)
|
|
33
|
-
return null
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const resolvePlugins = async (config: PluginConfig): Promise<Partial<Plugin>[]> => {
|
|
38
|
-
// 1. 处理数组形式配置 (直接实例化或函数调用)
|
|
39
|
-
if (Array.isArray(config)) {
|
|
40
|
-
return config.map((p) => (typeof p === 'function' ? p() : p))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 2. 处理对象形式配置 (动态加载)
|
|
44
|
-
const entries = Object.entries(config)
|
|
45
|
-
if (entries.length === 0) return []
|
|
46
|
-
|
|
47
|
-
// 并行加载所有插件
|
|
48
|
-
const modules = await Promise.allSettled(
|
|
49
|
-
entries.map(([pkgName, pkgConfig]) => {
|
|
50
|
-
// 如果不是以 @ 或 @vibe-forge/plugin- 开头,则默认加上 @vibe-forge/plugin- 前缀
|
|
51
|
-
const resolvedName = pkgName.startsWith('@') ? pkgName : `@vibe-forge/plugin-${pkgName}`
|
|
52
|
-
// dprint-ignore
|
|
53
|
-
return (
|
|
54
|
-
loadPlugin(resolvedName, pkgConfig as Record<string, unknown>) ??
|
|
55
|
-
loadPlugin(pkgName, pkgConfig as Record<string, unknown>)
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// 收集成功加载的插件
|
|
61
|
-
const plugins: Partial<Plugin>[] = []
|
|
62
|
-
|
|
63
|
-
modules.forEach((result, index) => {
|
|
64
|
-
const pkgName = entries[index][0]
|
|
65
|
-
if (result.status === 'fulfilled') {
|
|
66
|
-
if (result.value) {
|
|
67
|
-
plugins.push(result.value)
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
console.error(`Error loading plugin ${pkgName}:`, result.reason)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
return plugins
|
|
75
|
-
}
|
package/src/hooks/runtime.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
|
-
import process from 'node:process'
|
|
3
|
-
|
|
4
|
-
import { loadConfig, resetConfigCache } from '#~/config/load.js'
|
|
5
|
-
import { resolveServerLogLevel } from '#~/env.js'
|
|
6
|
-
import { createLogger } from '#~/utils/create-logger.js'
|
|
7
|
-
import { transformCamelKey } from '#~/utils/string-transform.js'
|
|
8
|
-
|
|
9
|
-
import type { HookInput, HookInputs, HookOutputCore, HookOutputs } from './type'
|
|
10
|
-
import type { HookContext, Plugin } from './index'
|
|
11
|
-
import { resolvePlugins } from './loader'
|
|
12
|
-
|
|
13
|
-
export const callPluginHook = async <K extends keyof HookInputs>(
|
|
14
|
-
eventName: K,
|
|
15
|
-
context: HookContext,
|
|
16
|
-
input: HookInputs[K],
|
|
17
|
-
plugins: Partial<Plugin>[] = []
|
|
18
|
-
): Promise<HookOutputs[K]> => {
|
|
19
|
-
const { logger } = context
|
|
20
|
-
const filteredPlugins = plugins.filter(
|
|
21
|
-
(
|
|
22
|
-
item
|
|
23
|
-
): item is
|
|
24
|
-
& {
|
|
25
|
-
name?: string
|
|
26
|
-
}
|
|
27
|
-
& {
|
|
28
|
-
[P in K]: NonNullable<Plugin[P]>
|
|
29
|
-
} => !!item && !!item[eventName]
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
let index = 0
|
|
33
|
-
|
|
34
|
-
const next = async (): Promise<HookOutputs[K]> => {
|
|
35
|
-
if (index >= filteredPlugins.length) {
|
|
36
|
-
return { continue: true }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const currentPlugin = filteredPlugins[index]
|
|
40
|
-
const { name = '<anonymous>', [eventName]: hook } = currentPlugin
|
|
41
|
-
index++
|
|
42
|
-
|
|
43
|
-
const withNameLogger = {
|
|
44
|
-
...logger,
|
|
45
|
-
info: logger.info.bind(logger, `[plugin.${name}]`),
|
|
46
|
-
warn: logger.warn.bind(logger, `[plugin.${name}]`),
|
|
47
|
-
debug: logger.debug.bind(logger, `[plugin.${name}]`),
|
|
48
|
-
error: logger.error.bind(logger, `[plugin.${name}]`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
return await hook(
|
|
53
|
-
{
|
|
54
|
-
...context,
|
|
55
|
-
logger: withNameLogger
|
|
56
|
-
},
|
|
57
|
-
input,
|
|
58
|
-
next
|
|
59
|
-
)
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof Error && !error.name.includes('[plugin.')) {
|
|
62
|
-
error.name = `${error.name}[plugin.${name}]`
|
|
63
|
-
}
|
|
64
|
-
throw error
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return next()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const executeHookInput = async (
|
|
72
|
-
input: HookInput,
|
|
73
|
-
env: Record<string, string | null | undefined> = process.env
|
|
74
|
-
) => {
|
|
75
|
-
const workspaceFolder = env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? input.cwd ?? process.env.HOME ?? '/'
|
|
76
|
-
const ctxId = env.__VF_PROJECT_AI_CTX_ID__ ?? input.sessionId ?? 'default'
|
|
77
|
-
const logPrefix = env.__VF_PROJECT_AI_LOG_PREFIX__ ?? ''
|
|
78
|
-
const loggerBase = createLogger(
|
|
79
|
-
workspaceFolder,
|
|
80
|
-
ctxId,
|
|
81
|
-
input.sessionId,
|
|
82
|
-
logPrefix,
|
|
83
|
-
resolveServerLogLevel(env)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
const logger: typeof loggerBase = {
|
|
87
|
-
...loggerBase,
|
|
88
|
-
info: (...args) => loggerBase.info(`[${input.hookEventName}]`, ...args),
|
|
89
|
-
warn: (...args) => loggerBase.warn(`[${input.hookEventName}]`, ...args),
|
|
90
|
-
debug: (...args) => loggerBase.debug(`[${input.hookEventName}]`, ...args),
|
|
91
|
-
error: (...args) => loggerBase.error(`[${input.hookEventName}]`, ...args)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
resetConfigCache()
|
|
95
|
-
const jsonVariables = {
|
|
96
|
-
...env,
|
|
97
|
-
WORKSPACE_FOLDER: workspaceFolder,
|
|
98
|
-
__VF_PROJECT_WORKSPACE_FOLDER__: workspaceFolder
|
|
99
|
-
}
|
|
100
|
-
const [config, userConfig] = await loadConfig({ jsonVariables })
|
|
101
|
-
const plugins = [
|
|
102
|
-
...await resolvePlugins(config?.plugins ?? []),
|
|
103
|
-
...await resolvePlugins(userConfig?.plugins ?? [])
|
|
104
|
-
]
|
|
105
|
-
|
|
106
|
-
return callPluginHook(
|
|
107
|
-
input.hookEventName,
|
|
108
|
-
{ logger },
|
|
109
|
-
input as HookInputs[typeof input.hookEventName],
|
|
110
|
-
plugins
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export const readHookInput = async () => {
|
|
115
|
-
const stdoutBuffer = await new Promise<Buffer>((resolve) => {
|
|
116
|
-
const chunks: Buffer[] = []
|
|
117
|
-
process.stdin.on('data', chunk => chunks.push(chunk))
|
|
118
|
-
process.stdin.once('end', () => resolve(Buffer.concat(chunks)))
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
return transformCamelKey<HookInput>(
|
|
122
|
-
JSON.parse(stdoutBuffer.toString() || '{}')
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const runHookCli = async () => {
|
|
127
|
-
try {
|
|
128
|
-
const input = await readHookInput()
|
|
129
|
-
const result = await executeHookInput(input)
|
|
130
|
-
console.log(JSON.stringify(result))
|
|
131
|
-
} catch (error) {
|
|
132
|
-
console.log(
|
|
133
|
-
JSON.stringify({
|
|
134
|
-
continue: false,
|
|
135
|
-
stopReason: `run hook error: ${String(error)}`
|
|
136
|
-
} satisfies HookOutputCore)
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
}
|
package/src/hooks/type.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { ToolInput, ToolOutput } from '../tools'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#hook-input
|
|
5
|
-
*/
|
|
6
|
-
export interface HookInputCore {
|
|
7
|
-
cwd: string
|
|
8
|
-
sessionId: string
|
|
9
|
-
hookEventName: keyof HookInputs
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface HookInputs {
|
|
13
|
-
/**
|
|
14
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#pretooluse-input
|
|
15
|
-
*/
|
|
16
|
-
PreToolUse: HookInputCore & ToolInput
|
|
17
|
-
/**
|
|
18
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#posttooluse-input
|
|
19
|
-
*/
|
|
20
|
-
PostToolUse: HookInputCore & ToolOutput
|
|
21
|
-
Notification: HookInputCore
|
|
22
|
-
UserPromptSubmit: HookInputCore & { prompt: string }
|
|
23
|
-
Stop: HookInputCore
|
|
24
|
-
SubagentStop: HookInputCore
|
|
25
|
-
PreCompact: HookInputCore
|
|
26
|
-
SessionStart: HookInputCore
|
|
27
|
-
SessionEnd: HookInputCore & {
|
|
28
|
-
reason: string
|
|
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
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type HookInput = HookInputs[keyof HookInputs]
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#common-json-fields
|
|
64
|
-
*/
|
|
65
|
-
export interface HookOutputCore {
|
|
66
|
-
/**
|
|
67
|
-
* Whether Claude should continue after hook execution
|
|
68
|
-
* @default true
|
|
69
|
-
*/
|
|
70
|
-
continue?: boolean
|
|
71
|
-
/**
|
|
72
|
-
* Message shown when continue is false
|
|
73
|
-
*/
|
|
74
|
-
stopReason?: string
|
|
75
|
-
/**
|
|
76
|
-
* Hide stdout from transcript mode
|
|
77
|
-
* @default false
|
|
78
|
-
*/
|
|
79
|
-
suppressOutput?: boolean
|
|
80
|
-
/**
|
|
81
|
-
* Optional warning message shown to the user
|
|
82
|
-
*/
|
|
83
|
-
systemMessage?: string
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface HookOutputs {
|
|
87
|
-
/**
|
|
88
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#pretooluse-decision-control
|
|
89
|
-
*/
|
|
90
|
-
PreToolUse: HookOutputCore & {
|
|
91
|
-
hookSpecificOutput?: {
|
|
92
|
-
hookEventName: 'PreToolUse'
|
|
93
|
-
permissionDecision: 'allow' | 'deny' | 'ask'
|
|
94
|
-
permissionDecisionReason: string
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* https://docs.anthropic.com/en/docs/claude-code/hooks#posttooluse-decision-control
|
|
99
|
-
*/
|
|
100
|
-
PostToolUse: HookOutputCore & {
|
|
101
|
-
hookSpecificOutput?: {
|
|
102
|
-
hookEventName: 'PostToolUse'
|
|
103
|
-
additionalContext: string
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
Notification: HookOutputCore
|
|
107
|
-
UserPromptSubmit: HookOutputCore
|
|
108
|
-
Stop: HookOutputCore
|
|
109
|
-
SessionStart: HookOutputCore
|
|
110
|
-
SessionEnd: HookOutputCore
|
|
111
|
-
SubagentStop: HookOutputCore
|
|
112
|
-
PreCompact: HookOutputCore
|
|
113
|
-
|
|
114
|
-
StartTasks: HookOutputCore
|
|
115
|
-
GenerateSystemPrompt: HookOutputCore
|
|
116
|
-
TaskStart: HookOutputCore
|
|
117
|
-
TaskStop: HookOutputCore
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export type HookOutput = HookOutputs[keyof HookOutputs]
|
package/src/utils/cache.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises'
|
|
2
|
-
import { dirname, resolve } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { Cache } from '@vibe-forge/core'
|
|
5
|
-
|
|
6
|
-
declare module '@vibe-forge/core' {
|
|
7
|
-
interface Cache {
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const getCachePath = (
|
|
12
|
-
cwd: string,
|
|
13
|
-
taskId: string,
|
|
14
|
-
sessionId: string | undefined,
|
|
15
|
-
key: keyof Cache
|
|
16
|
-
) => {
|
|
17
|
-
const taskDir = resolve(cwd, '.ai/caches', taskId)
|
|
18
|
-
const cacheDir = sessionId ? resolve(taskDir, sessionId) : taskDir
|
|
19
|
-
return resolve(cacheDir, `${key}.json`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const setCache = async <K extends keyof Cache>(
|
|
23
|
-
cwd: string,
|
|
24
|
-
taskId: string,
|
|
25
|
-
sessionId: string | undefined,
|
|
26
|
-
key: K,
|
|
27
|
-
value: Cache[K]
|
|
28
|
-
) => {
|
|
29
|
-
const cachePath = getCachePath(cwd, taskId, sessionId, key)
|
|
30
|
-
const cacheDir = dirname(cachePath)
|
|
31
|
-
try {
|
|
32
|
-
await fs.access(cacheDir)
|
|
33
|
-
} catch {
|
|
34
|
-
await fs.mkdir(cacheDir, { recursive: true })
|
|
35
|
-
}
|
|
36
|
-
await fs.writeFile(cachePath, JSON.stringify(value, null, 2), {
|
|
37
|
-
flag: 'w'
|
|
38
|
-
})
|
|
39
|
-
return { cachePath }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const getCache = async <K extends keyof Cache>(
|
|
43
|
-
cwd: string,
|
|
44
|
-
taskId: string,
|
|
45
|
-
sessionId: string | undefined,
|
|
46
|
-
key: K
|
|
47
|
-
): Promise<Cache[K] | undefined> => {
|
|
48
|
-
const cachePath = getCachePath(cwd, taskId, sessionId, key)
|
|
49
|
-
try {
|
|
50
|
-
await fs.access(cachePath)
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
53
|
-
return undefined
|
|
54
|
-
}
|
|
55
|
-
throw error
|
|
56
|
-
}
|
|
57
|
-
return JSON.parse(await fs.readFile(cachePath, 'utf-8'))
|
|
58
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { createWriteStream, existsSync, mkdirSync } from 'node:fs'
|
|
2
|
-
import { dirname, resolve } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { LogLevel } from '#~/env.js'
|
|
5
|
-
|
|
6
|
-
export interface Logger {
|
|
7
|
-
stream: NodeJS.WritableStream
|
|
8
|
-
info: (...args: unknown[]) => void
|
|
9
|
-
warn: (...args: unknown[]) => void
|
|
10
|
-
debug: (...args: unknown[]) => void
|
|
11
|
-
error: (...args: unknown[]) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
15
|
-
debug: 10,
|
|
16
|
-
info: 20,
|
|
17
|
-
warn: 30,
|
|
18
|
-
error: 40
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const createLogger = (
|
|
22
|
-
cwd: string,
|
|
23
|
-
taskId: string,
|
|
24
|
-
sessionId: string,
|
|
25
|
-
logPrefix = '',
|
|
26
|
-
level: LogLevel = 'info'
|
|
27
|
-
) => {
|
|
28
|
-
const normalizedSessionId = sessionId ?? 'default'
|
|
29
|
-
const taskDir = resolve(
|
|
30
|
-
cwd,
|
|
31
|
-
`.ai${logPrefix}/logs/${taskId}`
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
const loggerFilePath = resolve(
|
|
35
|
-
taskDir,
|
|
36
|
-
`${normalizedSessionId}.log.md`
|
|
37
|
-
)
|
|
38
|
-
// 默认日志文件不存在时,创建一个默认的日志文件
|
|
39
|
-
if (!existsSync(loggerFilePath)) {
|
|
40
|
-
mkdirSync(dirname(loggerFilePath), { recursive: true })
|
|
41
|
-
}
|
|
42
|
-
const loggerStream = createWriteStream(loggerFilePath, {
|
|
43
|
-
flags: 'a'
|
|
44
|
-
})
|
|
45
|
-
const createLog = (tag: string, currentLevel: LogLevel) => (...args: unknown[]) => {
|
|
46
|
-
if (LOG_LEVEL_PRIORITY[currentLevel] < LOG_LEVEL_PRIORITY[level]) {
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
const msg = args
|
|
50
|
-
.map((arg) => {
|
|
51
|
-
if (typeof arg === 'string') {
|
|
52
|
-
return arg
|
|
53
|
-
}
|
|
54
|
-
if (arg instanceof Error) {
|
|
55
|
-
return (
|
|
56
|
-
'\n```text\n' +
|
|
57
|
-
`${arg.stack}\n` +
|
|
58
|
-
'```'
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
return (
|
|
62
|
-
'\n```json\n' +
|
|
63
|
-
`${JSON.stringify(arg, null, 2)}\n` +
|
|
64
|
-
'```'
|
|
65
|
-
)
|
|
66
|
-
})
|
|
67
|
-
.join(' ')
|
|
68
|
-
const now = new Date().toLocaleString()
|
|
69
|
-
if (loggerStream.writableEnded) {
|
|
70
|
-
const tempLoggerStream = createWriteStream(loggerFilePath, {
|
|
71
|
-
flags: 'a'
|
|
72
|
-
})
|
|
73
|
-
tempLoggerStream.write(
|
|
74
|
-
`# [${now}] __E__ UNEXPECTED LOGGER STREAM ENDED\n`
|
|
75
|
-
)
|
|
76
|
-
tempLoggerStream.write(`# [${now}] __${tag}__ ${msg}\n`)
|
|
77
|
-
tempLoggerStream.end()
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
loggerStream.write(`# [${now}] __${tag}__ ${msg}\n`)
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
stream: loggerStream,
|
|
84
|
-
info: createLog('I', 'info'),
|
|
85
|
-
warn: createLog('W', 'warn'),
|
|
86
|
-
debug: createLog('D', 'debug'),
|
|
87
|
-
error: createLog('E', 'error')
|
|
88
|
-
}
|
|
89
|
-
}
|