@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "imports": {
5
5
  "#~/*.js": {
6
6
  "__vibe-forge__": {
@@ -22,22 +22,22 @@
22
22
  "require": "./dist/index.js"
23
23
  }
24
24
  },
25
- "./utils/*": {
25
+ "./adapter": {
26
26
  "__vibe-forge__": {
27
- "default": "./src/utils/*.ts"
27
+ "default": "./src/adapter/index.ts"
28
28
  },
29
29
  "default": {
30
- "import": "./dist/utils/*.mjs",
31
- "require": "./dist/utils/*.js"
30
+ "import": "./dist/adapter.mjs",
31
+ "require": "./dist/adapter.js"
32
32
  }
33
33
  },
34
- "./controllers/task": {
34
+ "./channel": {
35
35
  "__vibe-forge__": {
36
- "default": "./src/controllers/task/index.ts"
36
+ "default": "./src/channel.ts"
37
37
  },
38
38
  "default": {
39
- "import": "./dist/controllers/task/index.mjs",
40
- "require": "./dist/controllers/task/index.js"
39
+ "import": "./dist/channel.mjs",
40
+ "require": "./dist/channel.js"
41
41
  }
42
42
  },
43
43
  "./schema": {
@@ -57,7 +57,26 @@
57
57
  "import": "./dist/hooks/index.mjs",
58
58
  "require": "./dist/hooks/index.js"
59
59
  }
60
- }
60
+ },
61
+ "./utils/*": {
62
+ "__vibe-forge__": {
63
+ "default": "./src/utils/*.ts"
64
+ },
65
+ "default": {
66
+ "import": "./dist/utils/*.mjs",
67
+ "require": "./dist/utils/*.js"
68
+ }
69
+ },
70
+ "./controllers/*": {
71
+ "__vibe-forge__": {
72
+ "default": "./src/controllers/*/index.ts"
73
+ },
74
+ "default": {
75
+ "import": "./dist/controllers/*/index.mjs",
76
+ "require": "./dist/controllers/*/index.js"
77
+ }
78
+ },
79
+ "./package.json": "./package.json"
61
80
  },
62
81
  "dependencies": {
63
82
  "fast-glob": "^3.3.3",
@@ -10,7 +10,7 @@ export type AdapterOutputEvent =
10
10
  | { type: 'init'; data: SessionInitInfo }
11
11
  | { type: 'summary'; data: SessionSummaryInfo }
12
12
  | { type: 'message'; data: ChatMessage }
13
- | { type: 'exit'; data: { exitCode: number | null; stderr?: string } }
13
+ | { type: 'exit'; data: { exitCode?: number; stderr?: string } }
14
14
  | { type: 'stop'; data?: ChatMessage }
15
15
 
16
16
  export type SessionInfo =
@@ -66,6 +66,7 @@ export interface AdapterQueryOptions {
66
66
 
67
67
  systemPrompt?: string
68
68
  appendSystemPrompt?: boolean
69
+ permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
69
70
 
70
71
  mcpServers?: {
71
72
  include?: string[]
@@ -85,6 +86,10 @@ export interface AdapterQueryOptions {
85
86
  onEvent: (event: AdapterOutputEvent) => void
86
87
  }
87
88
 
89
+ export interface AdapterInitOptions {
90
+ force?: boolean
91
+ }
92
+
88
93
  export interface AdapterSession {
89
94
  kill: () => void
90
95
  emit: (event: AdapterEvent) => void
@@ -92,6 +97,10 @@ export interface AdapterSession {
92
97
  }
93
98
 
94
99
  export interface Adapter {
100
+ init?: (
101
+ ctx: AdapterCtx,
102
+ options: AdapterInitOptions
103
+ ) => Promise<void>
95
104
  query: (
96
105
  ctx: AdapterCtx,
97
106
  options: AdapterQueryOptions
package/src/channel.ts ADDED
@@ -0,0 +1,132 @@
1
+ import { z } from 'zod'
2
+
3
+ export interface ChannelMap {}
4
+
5
+ export type ChannelType = keyof ChannelMap
6
+
7
+ export const channelAccessSchema = z.object({
8
+ // 管理员
9
+ admins: z.array(z.string()).optional().describe(
10
+ '频道管理员账号(sender ID),管理员拥有管理操作权限且不受以下访问控制限制'
11
+ ),
12
+ // 会话类型控制
13
+ allowPrivateChat: z.boolean().optional().describe('是否允许私聊消息,默认 true'),
14
+ allowGroupChat: z.boolean().optional().describe('是否允许群聊消息,默认 true'),
15
+ // 群组访问控制
16
+ allowedGroups: z.array(z.string()).optional().describe('群组白名单(channel ID),设置后仅在指定群中响应'),
17
+ blockedGroups: z.array(z.string()).optional().describe('群组黑名单(channel ID),在指定群中不响应'),
18
+ // 用户访问控制
19
+ allowedSenders: z.array(z.string()).optional().describe('发送者白名单(sender ID),设置后仅白名单内的用户可交互'),
20
+ blockedSenders: z.array(z.string()).optional().describe('发送者黑名单(sender ID),黑名单内的用户消息将被忽略')
21
+ })
22
+
23
+ export type ChannelAccessConfig = z.infer<typeof channelAccessSchema>
24
+
25
+ export const channelBaseSchema = z.object({
26
+ // 基础配置
27
+ type: z
28
+ .string().min(1)
29
+ .describe('频道类型'),
30
+ title: z
31
+ .string().optional()
32
+ .describe('频道标题'),
33
+ description: z
34
+ .string().optional()
35
+ .describe('频道说明'),
36
+ enabled: z
37
+ .boolean().optional()
38
+ .describe('是否启用'),
39
+ // 会话行为
40
+ systemPrompt: z
41
+ .string().optional()
42
+ .describe('在此频道启动会话时注入的系统提示词'),
43
+ // 访问权限控制
44
+ access: channelAccessSchema
45
+ .optional()
46
+ .describe('频道访问权限配置')
47
+ })
48
+
49
+ export type ChannelBaseConfig = z.infer<typeof channelBaseSchema>
50
+
51
+ export type ChannelConfigByType<K extends ChannelType> = ChannelBaseConfig & { type: K } & ChannelMap[K]
52
+
53
+ export type ChannelConfig = {
54
+ [K in ChannelType]: ChannelConfigByType<K>
55
+ }[ChannelType]
56
+
57
+ export interface ChannelConnection<TMessage> {
58
+ sendMessage: (message: TMessage) => Promise<void>
59
+ startReceiving?: (options: {
60
+ handlers: ChannelEventHandlers
61
+ }) => Promise<void>
62
+ /**
63
+ * Called when a new session is being created for this channel.
64
+ * The channel implementation can use this to inject channel-specific context
65
+ * (e.g. bot profile fetched from platform API) into the system prompt.
66
+ */
67
+ generateSystemPrompt?: (inbound: ChannelInboundEvent) => Promise<string | undefined>
68
+ close?: () => Promise<void>
69
+ }
70
+
71
+ export interface ChannelLogger {
72
+ error: (...msg: unknown[]) => void | Promise<void>
73
+ warn: (...msg: unknown[]) => void | Promise<void>
74
+ info: (...msg: unknown[]) => void | Promise<void>
75
+ debug: (...msg: unknown[]) => void | Promise<void>
76
+ trace: (...msg: unknown[]) => void | Promise<void>
77
+ }
78
+
79
+ export interface ChannelConnectionOptions {
80
+ logger?: ChannelLogger
81
+ }
82
+
83
+ export interface ChannelInboundEvent {
84
+ channelType: string
85
+ sessionType: string
86
+ channelId: string
87
+ senderId?: string
88
+ messageId?: string
89
+ text?: string
90
+ replyTo?: {
91
+ receiveId: string
92
+ receiveIdType: string
93
+ }
94
+ ack?: () => Promise<void>
95
+ unack?: () => Promise<void>
96
+ raw: unknown
97
+ }
98
+
99
+ export type ChannelEventHandler<TPayload = unknown> = (payload: TPayload) => void | Promise<void>
100
+
101
+ export interface ChannelEventHandlers {
102
+ message?: ChannelEventHandler<ChannelInboundEvent>
103
+ [event: string]: ChannelEventHandler | ChannelEventHandler<ChannelInboundEvent> | undefined
104
+ }
105
+
106
+ export interface ChannelDescriptor<
107
+ TConfigSchema extends z.ZodTypeAny = z.ZodTypeAny,
108
+ TMessageSchema extends z.ZodTypeAny = z.ZodTypeAny,
109
+ > {
110
+ type: string
111
+ label: string
112
+ description?: string
113
+ configSchema: TConfigSchema
114
+ messageSchema: TMessageSchema
115
+ }
116
+
117
+ export const defineChannelDescriptor = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
118
+ descriptor: ChannelDescriptor<TConfigSchema, TMessageSchema>
119
+ ) => descriptor
120
+
121
+ export const defineChannel = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
122
+ descriptor: ChannelDescriptor<TConfigSchema, TMessageSchema>
123
+ ) => descriptor
124
+
125
+ export type ChannelCreateFn<TConfig = unknown, TMessage = unknown> = (
126
+ config: TConfig,
127
+ options?: ChannelConnectionOptions
128
+ ) => Promise<ChannelConnection<TMessage>>
129
+
130
+ export const defineCreateChannelConnection = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
131
+ connect: ChannelCreateFn<z.infer<TConfigSchema>, z.infer<TMessageSchema>>
132
+ ): ChannelCreateFn<z.infer<TConfigSchema>, z.infer<TMessageSchema>> => connect
@@ -0,0 +1,122 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+ import process from 'node:process'
5
+
6
+ import { load } from 'js-yaml'
7
+
8
+ import type { AdapterMap, Config } from './types'
9
+
10
+ const loadJSConfig = async (paths: string[]) => {
11
+ for (const path of paths) {
12
+ try {
13
+ const configPath = resolve(process.cwd(), path)
14
+ if (!existsSync(configPath)) {
15
+ continue
16
+ }
17
+ // eslint-disable-next-line ts/no-require-imports
18
+ return (require(configPath)?.default ?? {}) as Config
19
+ } catch (e) {
20
+ console.error(`Failed to load config file ${path}: ${e}`)
21
+ }
22
+ }
23
+ }
24
+
25
+ const loadJSONConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
26
+ for (const path of paths) {
27
+ try {
28
+ const configPath = resolve(process.cwd(), path)
29
+ if (!existsSync(configPath)) {
30
+ continue
31
+ }
32
+ const configContent = await readFile(configPath, 'utf-8')
33
+ const configResolvedContent = configContent
34
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
35
+ return JSON.parse(configResolvedContent) as Config
36
+ } catch (e) {
37
+ console.error(`Failed to load config file ${path}: ${e}`)
38
+ }
39
+ }
40
+ }
41
+
42
+ const loadYAMLConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
43
+ for (const path of paths) {
44
+ try {
45
+ const configPath = resolve(process.cwd(), path)
46
+ if (!existsSync(configPath)) {
47
+ continue
48
+ }
49
+ const configContent = await readFile(configPath, 'utf-8')
50
+ const configResolvedContent = configContent
51
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
52
+ return load(configResolvedContent) as Config
53
+ } catch (e) {
54
+ console.error(`Failed to load config file ${path}: ${e}`)
55
+ }
56
+ }
57
+ }
58
+
59
+ let configCache: Promise<readonly [Config | undefined, Config | undefined]> | null = null
60
+
61
+ export const resetConfigCache = () => {
62
+ configCache = null
63
+ }
64
+
65
+ export const loadConfig = (options: {
66
+ jsonVariables?: Record<string, string | null | undefined>
67
+ }) => {
68
+ if (configCache) {
69
+ return configCache
70
+ }
71
+
72
+ configCache = (async () =>
73
+ [
74
+ await loadJSONConfig(
75
+ [
76
+ './.ai.config.json',
77
+ './infra/.ai.config.json'
78
+ ],
79
+ options.jsonVariables ?? {}
80
+ ) ??
81
+ await loadYAMLConfig(
82
+ [
83
+ './.ai.config.yaml',
84
+ './.ai.config.yml',
85
+ './infra/.ai.config.yaml',
86
+ './infra/.ai.config.yml'
87
+ ],
88
+ options.jsonVariables ?? {}
89
+ ),
90
+ await loadJSONConfig(
91
+ [
92
+ './.ai.dev.config.json',
93
+ './infra/.ai.dev.config.json'
94
+ ],
95
+ options.jsonVariables ?? {}
96
+ ) ??
97
+ await loadYAMLConfig(
98
+ [
99
+ './.ai.dev.config.yaml',
100
+ './.ai.dev.config.yml',
101
+ './infra/.ai.dev.config.yaml',
102
+ './infra/.ai.dev.config.yml'
103
+ ],
104
+ options.jsonVariables ?? {}
105
+ )
106
+ ] as const)()
107
+ return configCache
108
+ }
109
+
110
+ export const loadAdapterConfig = async <
111
+ K extends keyof AdapterMap,
112
+ >(
113
+ name: K,
114
+ options: { jsonVariables?: Record<string, string> }
115
+ ) => {
116
+ const [projectConfig, userConfig] = await loadConfig(options)
117
+ return {
118
+ ...(projectConfig?.adapters?.[name] ?? {}),
119
+ ...(userConfig?.adapters?.[name] ?? {})
120
+ } as unknown as NonNullable<Config['adapters']>[K]
121
+ }
122
+
@@ -0,0 +1,269 @@
1
+ import type { ChannelConfig } from '../channel'
2
+ import type { PluginConfig } from '../hooks'
3
+
4
+ export interface AdapterMap {}
5
+
6
+ export interface AdapterBuiltinModel {
7
+ value: string
8
+ title: string
9
+ description: string
10
+ }
11
+
12
+ export interface ModelServiceConfig {
13
+ /**
14
+ * 模型服务展示标题
15
+ */
16
+ title?: string
17
+ /**
18
+ * 模型服务展示描述
19
+ */
20
+ description?: string
21
+ /**
22
+ * 模型服务 API 基础 URL
23
+ */
24
+ apiBaseUrl: string
25
+ /**
26
+ * 模型服务 API 密钥
27
+ */
28
+ apiKey: string
29
+ /**
30
+ * 模型服务支持的模型列表
31
+ */
32
+ models?: string[]
33
+ /**
34
+ * 模型服务支持的模型别名
35
+ */
36
+ modelsAlias?: Record<string, string[]>
37
+ /**
38
+ * 拓展配置,由下游自行消费
39
+ */
40
+ extra?: Record<string, unknown>
41
+ }
42
+
43
+ export interface RecommendedModelConfig {
44
+ service?: string
45
+ model: string
46
+ title?: string
47
+ description?: string
48
+ placement?: 'modelSelector'
49
+ }
50
+
51
+ export type LanguageCode = 'zh' | 'en'
52
+
53
+ export type NotificationTrigger = 'completed' | 'failed' | 'terminated' | 'waiting_input'
54
+
55
+ export interface NotificationEventConfig {
56
+ title?: string
57
+ description?: string
58
+ disabled?: boolean
59
+ sound?: string
60
+ }
61
+
62
+ export interface NotificationConfig {
63
+ disabled?: boolean
64
+ volume?: number
65
+ events?: Partial<Record<NotificationTrigger, NotificationEventConfig>>
66
+ }
67
+
68
+ export interface Config {
69
+ /**
70
+ * 配置目录
71
+ */
72
+ baseDir?: string
73
+ /**
74
+ * 适配器配置
75
+ */
76
+ adapters?: Partial<AdapterMap>
77
+ /**
78
+ * 默认适配器名称
79
+ */
80
+ defaultAdapter?: keyof AdapterMap
81
+ /**
82
+ * 模型服务配置
83
+ */
84
+ modelServices?: Record<string, ModelServiceConfig>
85
+ /**
86
+ * 频道配置
87
+ */
88
+ channels?: Record<string, ChannelConfig>
89
+ /**
90
+ * 默认模型服务名称
91
+ */
92
+ defaultModelService?: string
93
+ /**
94
+ * 默认模型名称
95
+ */
96
+ defaultModel?: string
97
+ recommendedModels?: RecommendedModelConfig[]
98
+ interfaceLanguage?: LanguageCode
99
+ modelLanguage?: LanguageCode
100
+ /**
101
+ * MCP 服务器配置
102
+ */
103
+ mcpServers?: Record<
104
+ string,
105
+ & {
106
+ /**
107
+ * 是否启用
108
+ */
109
+ enabled?: boolean
110
+ /**
111
+ * 环境变量配置
112
+ */
113
+ env?: Record<string, string>
114
+ }
115
+ & (
116
+ | {
117
+ type?: undefined
118
+ command: string
119
+ args: string[]
120
+ }
121
+ | {
122
+ type: 'sse'
123
+ url: string
124
+ headers: Record<string, string>
125
+ }
126
+ | {
127
+ type: 'http'
128
+ url: string
129
+ headers?: Record<string, string>
130
+ }
131
+ )
132
+ >
133
+ /**
134
+ * 默认启用的 MCP 服务器列表
135
+ */
136
+ defaultIncludeMcpServers?: string[]
137
+ /**
138
+ * 默认禁用的 MCP 服务器列表
139
+ */
140
+ defaultExcludeMcpServers?: string[]
141
+ noDefaultVibeForgeMcpServer?: boolean
142
+ /**
143
+ * 权限配置
144
+ */
145
+ permissions?: {
146
+ allow?: string[]
147
+ deny?: string[]
148
+ ask?: string[]
149
+ defaultMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
150
+ }
151
+ /**
152
+ * 环境变量配置
153
+ */
154
+ env?: Record<string, string>
155
+ /**
156
+ * 公告配置
157
+ */
158
+ announcements?: string[]
159
+ /**
160
+ * 快捷键配置
161
+ */
162
+ shortcuts?: {
163
+ newSession?: string
164
+ openConfig?: string
165
+ }
166
+ notifications?: NotificationConfig
167
+ /**
168
+ * 会话配置
169
+ */
170
+ conversation?: {
171
+ /**
172
+ * 对话风格
173
+ * - `friendly`: 友好的对话风格,适合用户与助手交互
174
+ * - `programmatic`: 程序化的对话风格,适合助手执行任务
175
+ */
176
+ style?: 'friendly' | 'programmatic'
177
+ /**
178
+ * 自定义对话风格。通过指定提示词约束对话风格。
179
+ */
180
+ customInstructions?: string
181
+ }
182
+ /**
183
+ * 插件配置
184
+ */
185
+ plugins?: PluginConfig
186
+ enabledPlugins?: Record<string, boolean>
187
+ extraKnownMarketplaces?: Record<
188
+ string,
189
+ {
190
+ source:
191
+ | {
192
+ source: 'github'
193
+ repo: string
194
+ }
195
+ | {
196
+ source: 'git'
197
+ url: string
198
+ }
199
+ | {
200
+ source: 'directory'
201
+ path: string
202
+ }
203
+ }
204
+ >
205
+ }
206
+
207
+ export interface AboutInfo {
208
+ version?: string
209
+ lastReleaseAt?: string
210
+ urls?: {
211
+ repo?: string
212
+ docs?: string
213
+ contact?: string
214
+ issues?: string
215
+ releases?: string
216
+ }
217
+ }
218
+
219
+ export interface ConfigSection {
220
+ general?: {
221
+ baseDir?: Config['baseDir']
222
+ defaultAdapter?: Config['defaultAdapter']
223
+ defaultModelService?: Config['defaultModelService']
224
+ defaultModel?: Config['defaultModel']
225
+ recommendedModels?: Config['recommendedModels']
226
+ interfaceLanguage?: Config['interfaceLanguage']
227
+ modelLanguage?: Config['modelLanguage']
228
+ announcements?: Config['announcements']
229
+ permissions?: Config['permissions']
230
+ env?: Config['env']
231
+ notifications?: Config['notifications']
232
+ }
233
+ conversation?: Config['conversation']
234
+ modelServices?: Config['modelServices']
235
+ channels?: Config['channels']
236
+ adapters?: Config['adapters']
237
+ adapterBuiltinModels?: Record<string, AdapterBuiltinModel[]>
238
+ plugins?: {
239
+ plugins?: Config['plugins']
240
+ enabledPlugins?: Config['enabledPlugins']
241
+ extraKnownMarketplaces?: Config['extraKnownMarketplaces']
242
+ }
243
+ mcp?: {
244
+ mcpServers?: Config['mcpServers']
245
+ defaultIncludeMcpServers?: Config['defaultIncludeMcpServers']
246
+ defaultExcludeMcpServers?: Config['defaultExcludeMcpServers']
247
+ noDefaultVibeForgeMcpServer?: Config['noDefaultVibeForgeMcpServer']
248
+ }
249
+ shortcuts?: Config['shortcuts']
250
+ }
251
+
252
+ export interface ConfigResponse {
253
+ sources?: {
254
+ project?: ConfigSection
255
+ user?: ConfigSection
256
+ merged?: ConfigSection
257
+ }
258
+ meta?: {
259
+ workspaceFolder?: string
260
+ configPresent?: {
261
+ project?: boolean
262
+ user?: boolean
263
+ }
264
+ experiments?: Record<string, unknown>
265
+ about?: AboutInfo
266
+ }
267
+ }
268
+
269
+ export const defineConfig = (config: Config) => config