@vibe-forge/core 2.0.0 → 2.0.2

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": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "imports": {
5
5
  "#~/*.js": {
6
6
  "__vibe-forge__": {
@@ -31,6 +31,15 @@
31
31
  "require": "./dist/channel.js"
32
32
  }
33
33
  },
34
+ "./config-schema": {
35
+ "__vibe-forge__": {
36
+ "default": "./src/config-schema.ts"
37
+ },
38
+ "default": {
39
+ "import": "./dist/config-schema.mjs",
40
+ "require": "./dist/config-schema.js"
41
+ }
42
+ },
34
43
  "./schema": {
35
44
  "__vibe-forge__": {
36
45
  "default": "./src/schema.ts"
@@ -44,7 +53,7 @@
44
53
  },
45
54
  "dependencies": {
46
55
  "zod": "^3.24.1",
47
- "@vibe-forge/utils": "^2.0.0",
48
- "@vibe-forge/types": "^2.0.0"
56
+ "@vibe-forge/types": "^2.0.1",
57
+ "@vibe-forge/utils": "^2.0.1"
49
58
  }
50
59
  }
@@ -0,0 +1,584 @@
1
+ /* eslint-disable max-lines -- central config schema registry */
2
+ import { z } from 'zod'
3
+
4
+ import type {
5
+ ConfigUiField,
6
+ ConfigUiFieldType,
7
+ ConfigUiObjectSchema,
8
+ ConfigUiRecordFieldSchema
9
+ } from '@vibe-forge/types'
10
+
11
+ import { channelBaseSchema } from './channel'
12
+
13
+ export interface ConfigSemanticIssue {
14
+ path?: string[]
15
+ message: string
16
+ }
17
+
18
+ type AdapterConfigSchemaKey<TSchema extends z.AnyZodObject> = Extract<keyof z.infer<TSchema>, string>
19
+
20
+ export interface AdapterConfigEntryMetadata<
21
+ TSchema extends z.AnyZodObject = z.AnyZodObject,
22
+ TExtraCommonKey extends AdapterConfigSchemaKey<TSchema> = never,
23
+ > {
24
+ extraCommonKeys?: readonly TExtraCommonKey[]
25
+ deepMergeKeys?: readonly AdapterConfigSchemaKey<TSchema>[]
26
+ }
27
+
28
+ export interface AdapterConfigContribution<
29
+ TSchema extends z.AnyZodObject = z.AnyZodObject,
30
+ TExtraCommonKey extends AdapterConfigSchemaKey<TSchema> = never,
31
+ > {
32
+ adapterKey: string
33
+ title?: string
34
+ description?: string
35
+ schema: TSchema
36
+ uiSchema?: ConfigUiObjectSchema
37
+ configEntry?: AdapterConfigEntryMetadata<TSchema, TExtraCommonKey>
38
+ validate?: (value: z.infer<TSchema>) => readonly ConfigSemanticIssue[] | void
39
+ }
40
+
41
+ export const defineAdapterConfigContribution = <
42
+ TSchema extends z.AnyZodObject,
43
+ TExtraCommonKey extends AdapterConfigSchemaKey<TSchema> = never,
44
+ >(
45
+ contribution: AdapterConfigContribution<TSchema, TExtraCommonKey>
46
+ ) => contribution
47
+
48
+ export const jsonValueSchema: z.ZodType<unknown> = z.lazy(() =>
49
+ z.union([
50
+ z.string(),
51
+ z.number(),
52
+ z.boolean(),
53
+ z.null(),
54
+ z.array(jsonValueSchema),
55
+ z.record(z.string(), jsonValueSchema)
56
+ ])
57
+ )
58
+
59
+ export const effortLevelSchema = z.enum(['low', 'medium', 'high', 'max'])
60
+ export const languageCodeSchema = z.enum(['zh', 'en'])
61
+
62
+ export const adapterAccountConfigCommonSchema = z.object({
63
+ title: z.string().optional().describe('Display title'),
64
+ description: z.string().optional().describe('Display description')
65
+ })
66
+
67
+ export const adapterConfigCommonSchema = z.object({
68
+ defaultModel: z.string().optional().describe('Default model override for this adapter'),
69
+ includeModels: z.array(z.string()).optional().describe('Allowed model IDs for this adapter'),
70
+ excludeModels: z.array(z.string()).optional().describe('Blocked model IDs for this adapter'),
71
+ defaultAccount: z.string().optional().describe('Default account override for this adapter'),
72
+ accounts: z.record(z.string(), adapterAccountConfigCommonSchema).optional()
73
+ .describe('Adapter account display metadata')
74
+ })
75
+
76
+ export const adapterNativeCliConfigSchema = z.object({
77
+ source: z.enum(['managed', 'system', 'path']).optional().describe('Native CLI source'),
78
+ path: z.string().optional().describe('Native CLI binary path when source is path'),
79
+ package: z.string().optional().describe('Managed npm package name'),
80
+ version: z.string().optional().describe('Managed npm package version'),
81
+ autoInstall: z.boolean().optional().describe('Install the managed CLI when no usable binary is found'),
82
+ prepareOnInstall: z.boolean().optional().describe('Preinstall this managed CLI during Vibe Forge package install'),
83
+ npmPath: z.string().optional().describe('npm binary used for managed installs')
84
+ })
85
+
86
+ export const modelServiceConfigSchema = z.object({
87
+ title: z.string().optional().describe('Display title'),
88
+ description: z.string().optional().describe('Display description'),
89
+ apiBaseUrl: z.string().min(1).describe('Provider API base URL'),
90
+ apiKey: z.string().min(1).describe('Provider API key'),
91
+ models: z.array(z.string()).optional().describe('Supported model IDs'),
92
+ timeoutMs: z.number().int().positive().optional().describe('Request timeout in milliseconds'),
93
+ maxOutputTokens: z.number().int().positive().optional().describe('Default max output tokens'),
94
+ extra: z.record(z.string(), jsonValueSchema).optional().describe('Provider-specific extra config')
95
+ })
96
+
97
+ export const recommendedModelConfigSchema = z.object({
98
+ service: z.string().optional().describe('Model service key'),
99
+ model: z.string().min(1).describe('Model ID'),
100
+ title: z.string().optional().describe('Display title'),
101
+ description: z.string().optional().describe('Display description'),
102
+ placement: z.enum(['modelSelector']).optional().describe('UI placement')
103
+ })
104
+
105
+ export const modelMetadataConfigSchema = z.object({
106
+ alias: z.union([z.string(), z.array(z.string())]).optional().describe('Model aliases'),
107
+ title: z.string().optional().describe('Display title'),
108
+ description: z.string().optional().describe('Display description'),
109
+ defaultAdapter: z.string().optional().describe('Preferred adapter key'),
110
+ effort: effortLevelSchema.optional().describe('Recommended effort level')
111
+ })
112
+
113
+ export const notificationEventConfigSchema = z.object({
114
+ title: z.string().optional().describe('Notification title override'),
115
+ description: z.string().optional().describe('Notification description override'),
116
+ disabled: z.boolean().optional().describe('Disable this notification event'),
117
+ sound: z.string().optional().describe('Custom sound asset')
118
+ })
119
+
120
+ export const notificationConfigSchema = z.object({
121
+ disabled: z.boolean().optional().describe('Disable notifications'),
122
+ volume: z.number().min(0).max(100).optional().describe('Notification volume'),
123
+ events: z.object({
124
+ completed: notificationEventConfigSchema.optional(),
125
+ failed: notificationEventConfigSchema.optional(),
126
+ terminated: notificationEventConfigSchema.optional(),
127
+ waiting_input: notificationEventConfigSchema.optional()
128
+ }).optional().describe('Per-event notification overrides')
129
+ })
130
+
131
+ export const permissionsConfigSchema = z.object({
132
+ allow: z.array(z.string()).optional().describe('Allowed tools'),
133
+ deny: z.array(z.string()).optional().describe('Denied tools'),
134
+ ask: z.array(z.string()).optional().describe('Tools that always ask'),
135
+ defaultMode: z.enum(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions']).optional()
136
+ .describe('Default permission mode')
137
+ })
138
+
139
+ export const shortcutsConfigSchema = z.object({
140
+ newSession: z.string().optional().describe('Shortcut for creating a new session'),
141
+ openConfig: z.string().optional().describe('Shortcut for opening config'),
142
+ sendMessage: z.string().optional().describe('Shortcut for sending a message'),
143
+ clearInput: z.string().optional().describe('Shortcut for clearing the composer'),
144
+ switchModel: z.string().optional().describe('Shortcut for switching models'),
145
+ switchEffort: z.string().optional().describe('Shortcut for switching effort'),
146
+ switchPermissionMode: z.string().optional().describe('Shortcut for switching permission mode')
147
+ })
148
+
149
+ export const conversationStarterModeSchema = z.enum([
150
+ 'default',
151
+ 'workspace',
152
+ 'entity',
153
+ 'agent',
154
+ 'spec'
155
+ ])
156
+
157
+ export const conversationStarterWorktreeConfigSchema = z.object({
158
+ create: z.boolean().optional().describe('Override whether the session uses a managed worktree'),
159
+ environment: z.string().optional().describe('Managed worktree environment override'),
160
+ branch: z.object({
161
+ name: z.string().min(1).describe('Branch name'),
162
+ kind: z.enum(['local', 'remote']).optional().describe('Branch kind'),
163
+ mode: z.enum(['checkout', 'create']).optional().describe('Branch operation mode')
164
+ }).optional().describe('Branch selection override')
165
+ })
166
+
167
+ export const conversationStarterConfigSchema = z.object({
168
+ id: z.string().optional().describe('Stable starter identifier'),
169
+ title: z.string().min(1).describe('Starter title'),
170
+ description: z.string().optional().describe('Starter description'),
171
+ icon: z.string().optional().describe('Material Symbols icon name'),
172
+ mode: conversationStarterModeSchema.optional().describe('Target mode, `agent` is an alias for `entity`'),
173
+ target: z.string().optional().describe('Target resource name or workspace id'),
174
+ targetLabel: z.string().optional().describe('Optional target label shown in the UI'),
175
+ targetDescription: z.string().optional().describe('Optional target description shown in the UI'),
176
+ model: z.string().optional().describe('Model id or service-prefixed model value'),
177
+ adapter: z.string().optional().describe('Adapter override'),
178
+ account: z.string().optional().describe('Account override'),
179
+ effort: z.union([z.literal('default'), effortLevelSchema]).optional().describe('Effort override'),
180
+ permissionMode: z.enum(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions']).optional()
181
+ .describe('Permission mode override'),
182
+ worktree: conversationStarterWorktreeConfigSchema.optional().describe('Managed worktree overrides'),
183
+ prompt: z.string().optional().describe('Prefilled prompt'),
184
+ files: z.array(z.string()).optional().describe('Referenced file paths'),
185
+ rules: z.array(z.string()).optional().describe('Referenced rule paths or rule identifiers'),
186
+ skills: z.array(z.string()).optional().describe('Referenced skill paths or skill identifiers')
187
+ })
188
+
189
+ export const conversationConfigSchema = z.object({
190
+ style: z.enum(['friendly', 'programmatic']).optional().describe('Conversation style'),
191
+ customInstructions: z.string().optional().describe('Extra system instructions'),
192
+ injectDefaultSystemPrompt: z.boolean().optional().describe('Inject the default system prompt'),
193
+ createSessionWorktree: z.boolean().optional().describe('Create a managed worktree for new sessions by default'),
194
+ worktreeEnvironment: z.string().optional().describe('Default managed worktree environment'),
195
+ startupPresets: z.array(conversationStarterConfigSchema).optional()
196
+ .describe('Quick-start presets shown on the new session page'),
197
+ builtinActions: z.array(conversationStarterConfigSchema).optional()
198
+ .describe('Built-in development actions shown on the new session page')
199
+ })
200
+
201
+ export const webAuthAccountConfigSchema = z.object({
202
+ username: z.string().min(1).describe('Login username'),
203
+ password: z.string().min(1).describe('Login password')
204
+ })
205
+
206
+ export const webAuthConfigSchema = z.object({
207
+ enabled: z.boolean().optional().describe('Enable Web UI login protection'),
208
+ username: z.string().optional().describe('Fallback single-account username'),
209
+ password: z.string().optional().describe('Fallback single-account password'),
210
+ accounts: z.array(webAuthAccountConfigSchema).optional().describe('Allowed Web UI login accounts'),
211
+ sessionTtlHours: z.number().positive().optional().describe('Browser session token lifetime in hours'),
212
+ rememberDeviceTtlDays: z.number().positive().optional().describe('Remember-device token lifetime in days')
213
+ })
214
+
215
+ export const skillsCliConfigSchema = adapterNativeCliConfigSchema.extend({
216
+ registry: z.string().optional().describe('Package registry used to install the managed skills CLI'),
217
+ npmRegistry: z.string().optional().describe('Deprecated alias for skillsCli.registry'),
218
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables passed to the skills CLI')
219
+ })
220
+
221
+ export const skillHomeBridgeConfigSchema = z.object({
222
+ enabled: z.boolean().optional().describe('Bridge supported home skill roots into workspace asset discovery'),
223
+ roots: z.union([z.string(), z.array(z.string())]).optional()
224
+ .describe('Ordered home skill roots. Supports absolute paths or paths starting with ~')
225
+ })
226
+
227
+ export const configuredSkillInstallConfigSchema = z.union([
228
+ z.string().min(1),
229
+ z.object({
230
+ name: z.string().min(1).describe('Remote skill name'),
231
+ source: z.string().optional().describe('Remote skills CLI source path'),
232
+ rename: z.string().optional().describe('Local skill name to expose after install')
233
+ })
234
+ ])
235
+
236
+ export const legacySkillsConfigSchema = z.object({
237
+ install: z.array(configuredSkillInstallConfigSchema).optional()
238
+ .describe('Project skills that should be ensured before session startup'),
239
+ cli: skillsCliConfigSchema.optional().describe('Deprecated alias for top-level skillsCli runtime settings'),
240
+ homeBridge: skillHomeBridgeConfigSchema.optional().describe('Home skill auto-bridge settings')
241
+ })
242
+
243
+ export const skillsConfigSchema = z.union([
244
+ z.array(configuredSkillInstallConfigSchema)
245
+ .describe('Project skills that should be ensured before session startup'),
246
+ legacySkillsConfigSchema
247
+ ])
248
+
249
+ const pluginInstanceConfigSchema: z.ZodType<unknown> = z.lazy(() =>
250
+ z.object({
251
+ id: z.string().min(1).describe('Plugin package name or short id'),
252
+ enabled: z.boolean().optional().describe('Disable this plugin instance'),
253
+ scope: z.string().optional().describe('User-defined plugin scope'),
254
+ options: z.record(z.string(), jsonValueSchema).optional().describe('Plugin instance options'),
255
+ children: z.array(pluginInstanceConfigSchema).optional().describe('Nested child plugin overrides')
256
+ })
257
+ )
258
+
259
+ export const pluginConfigSchema = z.array(pluginInstanceConfigSchema).describe('Plugin instance list')
260
+
261
+ const marketplacePluginSourceSchema = z.union([
262
+ z.string().min(1),
263
+ z.object({
264
+ source: z.literal('github'),
265
+ repo: z.string().min(1),
266
+ ref: z.string().optional(),
267
+ sha: z.string().optional()
268
+ }),
269
+ z.object({
270
+ source: z.literal('url'),
271
+ url: z.string().min(1),
272
+ ref: z.string().optional(),
273
+ sha: z.string().optional()
274
+ }),
275
+ z.object({
276
+ source: z.literal('git-subdir'),
277
+ url: z.string().min(1),
278
+ path: z.string().min(1),
279
+ ref: z.string().optional(),
280
+ sha: z.string().optional()
281
+ }),
282
+ z.object({
283
+ source: z.literal('npm'),
284
+ package: z.string().min(1),
285
+ version: z.string().optional(),
286
+ registry: z.string().optional()
287
+ })
288
+ ])
289
+
290
+ const marketplacePluginDefinitionSchema = z.object({
291
+ name: z.string().min(1),
292
+ description: z.string().optional(),
293
+ version: z.string().optional(),
294
+ strict: z.boolean().optional(),
295
+ skills: z.union([z.string(), z.array(z.string())]).optional(),
296
+ commands: z.union([z.string(), z.array(z.string())]).optional(),
297
+ agents: z.union([z.string(), z.array(z.string())]).optional(),
298
+ hooks: z.union([z.string(), z.array(z.string()), z.record(z.string(), jsonValueSchema)]).optional(),
299
+ mcpServers: z.union([z.string(), z.array(z.string()), z.record(z.string(), jsonValueSchema)]).optional(),
300
+ userConfig: jsonValueSchema.optional(),
301
+ source: marketplacePluginSourceSchema
302
+ })
303
+
304
+ const marketplaceSourceSchema = z.union([
305
+ z.object({
306
+ source: z.literal('github'),
307
+ repo: z.string().min(1),
308
+ ref: z.string().optional(),
309
+ path: z.string().optional()
310
+ }),
311
+ z.object({
312
+ source: z.literal('git'),
313
+ url: z.string().min(1),
314
+ ref: z.string().optional(),
315
+ path: z.string().optional()
316
+ }),
317
+ z.object({
318
+ source: z.literal('directory'),
319
+ path: z.string().min(1)
320
+ }),
321
+ z.object({
322
+ source: z.literal('url'),
323
+ url: z.string().min(1)
324
+ }),
325
+ z.object({
326
+ source: z.literal('settings'),
327
+ name: z.string().optional(),
328
+ metadata: z.object({
329
+ pluginRoot: z.string().optional()
330
+ }).optional(),
331
+ plugins: z.array(marketplacePluginDefinitionSchema)
332
+ }),
333
+ z.object({
334
+ source: z.literal('hostPattern'),
335
+ hostPattern: z.string().min(1)
336
+ })
337
+ ])
338
+
339
+ const marketplaceDeclaredPluginConfigSchema = z.union([
340
+ z.boolean().transform(enabled => ({ enabled })),
341
+ z.object({
342
+ enabled: z.boolean().optional(),
343
+ scope: z.string().optional()
344
+ })
345
+ ])
346
+
347
+ export const marketplaceConfigSchema = z.record(
348
+ z.string(),
349
+ z.object({
350
+ type: z.literal('claude-code'),
351
+ enabled: z.boolean().optional(),
352
+ syncOnRun: z.boolean().optional(),
353
+ plugins: z.record(z.string(), marketplaceDeclaredPluginConfigSchema).optional(),
354
+ options: z.object({
355
+ source: marketplaceSourceSchema
356
+ }).optional()
357
+ })
358
+ )
359
+
360
+ const mcpServerCommonSchema = z.object({
361
+ enabled: z.boolean().optional().describe('Enable this MCP server'),
362
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables')
363
+ })
364
+
365
+ const mcpServerCommandSchema = mcpServerCommonSchema.extend({
366
+ command: z.string().min(1).describe('Executable command'),
367
+ args: z.array(z.string()).optional().describe('Command arguments')
368
+ })
369
+
370
+ const mcpServerSseSchema = mcpServerCommonSchema.extend({
371
+ type: z.literal('sse'),
372
+ url: z.string().min(1).describe('SSE endpoint URL'),
373
+ headers: z.record(z.string(), z.string()).describe('HTTP headers')
374
+ })
375
+
376
+ const mcpServerHttpSchema = mcpServerCommonSchema.extend({
377
+ type: z.literal('http'),
378
+ url: z.string().min(1).describe('HTTP endpoint URL'),
379
+ headers: z.record(z.string(), z.string()).optional().describe('HTTP headers')
380
+ })
381
+
382
+ export const mcpServerConfigSchema = z.union([
383
+ mcpServerCommandSchema,
384
+ mcpServerSseSchema,
385
+ mcpServerHttpSchema
386
+ ])
387
+
388
+ export const generalConfigSectionSchema = z.object({
389
+ baseDir: z.string().optional(),
390
+ effort: effortLevelSchema.optional(),
391
+ defaultAdapter: z.string().optional(),
392
+ defaultModelService: z.string().optional(),
393
+ defaultModel: z.string().optional(),
394
+ recommendedModels: z.array(recommendedModelConfigSchema).optional(),
395
+ interfaceLanguage: languageCodeSchema.optional(),
396
+ modelLanguage: languageCodeSchema.optional(),
397
+ announcements: z.array(z.string()).optional(),
398
+ permissions: permissionsConfigSchema.optional(),
399
+ env: z.record(z.string(), z.string()).optional(),
400
+ notifications: notificationConfigSchema.optional(),
401
+ skills: skillsConfigSchema.optional(),
402
+ skillsCli: skillsCliConfigSchema.optional(),
403
+ webAuth: webAuthConfigSchema.optional()
404
+ })
405
+
406
+ export const pluginSectionSchema = z.object({
407
+ plugins: pluginConfigSchema.optional(),
408
+ marketplaces: marketplaceConfigSchema.optional()
409
+ })
410
+
411
+ export const mcpConfigSectionSchema = z.object({
412
+ mcpServers: z.record(z.string(), mcpServerConfigSchema).optional(),
413
+ defaultIncludeMcpServers: z.array(z.string()).optional(),
414
+ defaultExcludeMcpServers: z.array(z.string()).optional(),
415
+ noDefaultVibeForgeMcpServer: z.boolean().optional()
416
+ })
417
+
418
+ export const baseAdapterEntrySchema = adapterConfigCommonSchema.passthrough()
419
+ export const baseChannelEntrySchema = channelBaseSchema.passthrough()
420
+
421
+ export const configSectionSchemas = {
422
+ general: generalConfigSectionSchema,
423
+ conversation: conversationConfigSchema,
424
+ models: z.record(z.string(), modelMetadataConfigSchema),
425
+ modelServices: z.record(z.string(), modelServiceConfigSchema),
426
+ channels: z.record(z.string(), baseChannelEntrySchema),
427
+ adapters: z.object({}).catchall(baseAdapterEntrySchema),
428
+ plugins: pluginSectionSchema,
429
+ mcp: mcpConfigSectionSchema,
430
+ auth: webAuthConfigSchema,
431
+ shortcuts: shortcutsConfigSchema
432
+ } as const
433
+
434
+ export const baseConfigFileSchema = z.object({
435
+ $schema: z.string().optional().describe('JSON Schema URL'),
436
+ extend: z.union([z.string().min(1), z.array(z.string().min(1))]).optional(),
437
+ baseDir: z.string().optional(),
438
+ effort: effortLevelSchema.optional(),
439
+ adapters: z.object({}).catchall(baseAdapterEntrySchema).optional(),
440
+ models: z.record(z.string(), modelMetadataConfigSchema).optional(),
441
+ defaultAdapter: z.string().optional(),
442
+ modelServices: z.record(z.string(), modelServiceConfigSchema).optional(),
443
+ channels: z.record(z.string(), baseChannelEntrySchema).optional(),
444
+ defaultModelService: z.string().optional(),
445
+ defaultModel: z.string().optional(),
446
+ recommendedModels: z.array(recommendedModelConfigSchema).optional(),
447
+ interfaceLanguage: languageCodeSchema.optional(),
448
+ modelLanguage: languageCodeSchema.optional(),
449
+ mcpServers: z.record(z.string(), mcpServerConfigSchema).optional(),
450
+ defaultIncludeMcpServers: z.array(z.string()).optional(),
451
+ defaultExcludeMcpServers: z.array(z.string()).optional(),
452
+ noDefaultVibeForgeMcpServer: z.boolean().optional(),
453
+ permissions: permissionsConfigSchema.optional(),
454
+ env: z.record(z.string(), z.string()).optional(),
455
+ announcements: z.array(z.string()).optional(),
456
+ shortcuts: shortcutsConfigSchema.optional(),
457
+ notifications: notificationConfigSchema.optional(),
458
+ skills: skillsConfigSchema.optional(),
459
+ skillsCli: skillsCliConfigSchema.optional(),
460
+ webAuth: webAuthConfigSchema.optional(),
461
+ conversation: conversationConfigSchema.optional(),
462
+ plugins: pluginConfigSchema.optional(),
463
+ marketplaces: marketplaceConfigSchema.optional()
464
+ }).strict()
465
+
466
+ const getZodTypeName = (schema: z.ZodTypeAny) => (
467
+ (schema as { _def?: { typeName?: string } })._def?.typeName
468
+ )
469
+
470
+ const isZodType = (schema: z.ZodTypeAny, typeName: string) => getZodTypeName(schema) === typeName
471
+
472
+ const unwrapUiSchema = (schema: z.ZodTypeAny): z.ZodTypeAny => {
473
+ if (isZodType(schema, 'ZodOptional') || isZodType(schema, 'ZodNullable')) {
474
+ return unwrapUiSchema((schema as unknown as { unwrap: () => z.ZodTypeAny }).unwrap())
475
+ }
476
+ if (isZodType(schema, 'ZodDefault')) {
477
+ return unwrapUiSchema((schema as unknown as { removeDefault: () => z.ZodTypeAny }).removeDefault())
478
+ }
479
+ if (isZodType(schema, 'ZodEffects')) {
480
+ return unwrapUiSchema((schema as unknown as { innerType: () => z.ZodTypeAny }).innerType())
481
+ }
482
+ return schema
483
+ }
484
+
485
+ const getUiDefaultValue = (schema: z.ZodTypeAny): unknown => {
486
+ if (isZodType(schema, 'ZodDefault')) {
487
+ return (schema as unknown as { _def: { defaultValue: () => unknown } })._def.defaultValue()
488
+ }
489
+ if (isZodType(schema, 'ZodOptional')) return undefined
490
+ if (isZodType(schema, 'ZodNullable')) return null
491
+
492
+ const unwrapped = unwrapUiSchema(schema)
493
+ if (isZodType(unwrapped, 'ZodLiteral')) return (unwrapped as unknown as { value: unknown }).value
494
+ if (isZodType(unwrapped, 'ZodEnum')) return (unwrapped as unknown as { options: string[] }).options[0]
495
+ if (isZodType(unwrapped, 'ZodNativeEnum')) {
496
+ const values = Object.values((unwrapped as unknown as { enum: Record<string, unknown> }).enum)
497
+ return values.length > 0 ? values[0] : undefined
498
+ }
499
+ if (isZodType(unwrapped, 'ZodString')) return ''
500
+ if (isZodType(unwrapped, 'ZodNumber')) return 0
501
+ if (isZodType(unwrapped, 'ZodBoolean')) return false
502
+ if (isZodType(unwrapped, 'ZodArray')) return []
503
+ if (isZodType(unwrapped, 'ZodObject') || isZodType(unwrapped, 'ZodRecord')) return {}
504
+ return undefined
505
+ }
506
+
507
+ const inferUiFieldType = (schema: z.ZodTypeAny): ConfigUiFieldType => {
508
+ const unwrapped = unwrapUiSchema(schema)
509
+ if (isZodType(unwrapped, 'ZodString')) return 'string'
510
+ if (isZodType(unwrapped, 'ZodNumber')) return 'number'
511
+ if (isZodType(unwrapped, 'ZodBoolean')) return 'boolean'
512
+ if (
513
+ isZodType(unwrapped, 'ZodEnum') ||
514
+ isZodType(unwrapped, 'ZodNativeEnum') ||
515
+ isZodType(unwrapped, 'ZodLiteral')
516
+ ) {
517
+ return 'select'
518
+ }
519
+ if (isZodType(unwrapped, 'ZodArray')) {
520
+ const element = unwrapUiSchema((unwrapped as unknown as { element: z.ZodTypeAny }).element)
521
+ return isZodType(element, 'ZodString') ? 'string[]' : 'json'
522
+ }
523
+ return 'json'
524
+ }
525
+
526
+ const inferUiOptions = (schema: z.ZodTypeAny) => {
527
+ const unwrapped = unwrapUiSchema(schema)
528
+ if (isZodType(unwrapped, 'ZodLiteral')) {
529
+ return [{ value: String((unwrapped as unknown as { value: unknown }).value) }]
530
+ }
531
+ if (isZodType(unwrapped, 'ZodEnum')) {
532
+ return (unwrapped as unknown as { options: string[] }).options.map(value => ({ value }))
533
+ }
534
+ if (isZodType(unwrapped, 'ZodNativeEnum')) {
535
+ return Object.values((unwrapped as unknown as { enum: Record<string, string | number> }).enum)
536
+ .map(value => ({ value: String(value) }))
537
+ }
538
+ return undefined
539
+ }
540
+
541
+ export const buildConfigUiObjectSchema = (schema: z.ZodTypeAny): ConfigUiObjectSchema => {
542
+ const unwrapped = unwrapUiSchema(schema)
543
+ if (!isZodType(unwrapped, 'ZodObject')) {
544
+ return { fields: [] }
545
+ }
546
+
547
+ const shapeEntries = Object.entries((unwrapped as unknown as { shape: Record<string, z.ZodTypeAny> }).shape) as Array<
548
+ [string, z.ZodTypeAny]
549
+ >
550
+ const fields = shapeEntries.map(([key, value]) => {
551
+ const uiField: ConfigUiField = {
552
+ path: [key],
553
+ type: inferUiFieldType(value),
554
+ defaultValue: getUiDefaultValue(value),
555
+ description: unwrapUiSchema(value).description,
556
+ options: inferUiOptions(value)
557
+ }
558
+ return uiField
559
+ })
560
+
561
+ const recordFields = Object.fromEntries(
562
+ shapeEntries.flatMap(([key, value]): Array<[string, ConfigUiRecordFieldSchema]> => {
563
+ const recordSchema = unwrapUiSchema(value)
564
+ if (!isZodType(recordSchema, 'ZodRecord')) {
565
+ return []
566
+ }
567
+
568
+ const itemSchema = (recordSchema as unknown as { _def: { valueType: z.ZodTypeAny } })._def.valueType
569
+ const itemObjectSchema = buildConfigUiObjectSchema(itemSchema)
570
+ if ((itemObjectSchema.fields.length === 0) && itemObjectSchema.recordFields == null) {
571
+ return []
572
+ }
573
+
574
+ return [[key, {
575
+ itemSchema: itemObjectSchema
576
+ }]]
577
+ })
578
+ )
579
+
580
+ return {
581
+ fields,
582
+ ...(Object.keys(recordFields).length > 0 ? { recordFields } : {})
583
+ }
584
+ }
package/src/env.ts CHANGED
@@ -17,7 +17,7 @@ export interface ServerEnv {
17
17
  __VF_PROJECT_AI_SERVER_LOG_LEVEL__: LogLevel
18
18
  __VF_PROJECT_AI_SERVER_DEBUG__: boolean
19
19
  __VF_PROJECT_AI_SERVER_ALLOW_CORS__: boolean
20
- __VF_PROJECT_AI_CLIENT_MODE__?: 'dev' | 'static'
20
+ __VF_PROJECT_AI_CLIENT_MODE__?: 'dev' | 'none' | 'static' | 'standalone' | 'independent' | 'desktop'
21
21
  __VF_PROJECT_AI_CLIENT_BASE__?: string
22
22
  __VF_PROJECT_AI_CLIENT_DIST_PATH__?: string
23
23
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './config-schema'
1
2
  export * from './env'
2
3
  export * from './schema'
3
4
  export * from './tools'
package/src/tools.ts CHANGED
@@ -5,7 +5,7 @@ export interface StopTaskToolInput {
5
5
  export interface StartTasksToolInput {
6
6
  tasks: Array<{
7
7
  description?: string
8
- type?: 'default' | 'spec' | 'entity'
8
+ type?: 'default' | 'spec' | 'entity' | 'workspace'
9
9
  name?: string
10
10
  adapter?: string
11
11
  permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
package/src/types.ts CHANGED
@@ -8,11 +8,13 @@ export type {
8
8
  Session,
9
9
  SessionMessageQueueState,
10
10
  SessionPermissionMode,
11
+ SessionPromptType,
11
12
  SessionQueuedMessage,
12
13
  SessionQueuedMessageMode,
13
14
  SessionStatus,
14
15
  SessionWorkspace,
15
16
  SessionWorkspaceCleanupPolicy,
17
+ SessionWorkspaceFileState,
16
18
  SessionWorkspaceKind,
17
19
  SessionWorkspaceState,
18
20
  TaskDetail,