fix: harden scoped tools and settings persistence

This commit is contained in:
lofyer
2026-08-13 14:56:53 +08:00
parent bf1ec5d2f1
commit aab961226f
62 changed files with 4343 additions and 1314 deletions
+9 -2
View File
@@ -1,5 +1,6 @@
import { z } from 'zod'
import { magicNoteCommentFormatSchema } from './magic-notes-contracts'
import { settingsWarningsSchema } from './settings-warning-contracts'
export const magicNoteCommentModeSchema = z.enum([
'immediate',
@@ -11,7 +12,7 @@ export type MagicNoteCommentMode = z.infer<
typeof magicNoteCommentModeSchema
>
export const applicationSettingsSchema = z
const applicationPreferencesSchema = z
.object({
checkUpdatesOnStartup: z.boolean(),
magicNotesEnabled: z.boolean(),
@@ -20,7 +21,13 @@ export const applicationSettingsSchema = z
})
.strict()
export const applicationSettingsUpdateSchema = applicationSettingsSchema
export const applicationSettingsSchema = applicationPreferencesSchema
.extend({
warnings: settingsWarningsSchema.optional()
})
.strict()
export const applicationSettingsUpdateSchema = applicationPreferencesSchema
.partial()
.refine((input) => Object.keys(input).length > 0, {
message: 'At least one application setting is required'
+3 -1
View File
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { settingsWarningsSchema } from './settings-warning-contracts'
const controlCharacterFreeString = (maximumLength: number) =>
z
@@ -331,7 +332,8 @@ export const capabilitySnapshotSchema = z
.array(computerCapabilityConfigSummarySchema)
.max(2)
.optional(),
browserProfiles: browserProfilesSummarySchema.optional()
browserProfiles: browserProfilesSummarySchema.optional(),
warnings: settingsWarningsSchema.optional()
})
.strict()
export type CapabilitySnapshot = z.infer<typeof capabilitySnapshotSchema>
+4 -7
View File
@@ -3,6 +3,7 @@ import {
projectChannelSchema,
type ProjectChannel
} from './assistant-contracts'
import { settingsWarningsSchema } from './settings-warning-contracts'
export const CHANNEL_SETTINGS_LIMITS = {
maximumIdentifierLength: 256,
@@ -106,7 +107,8 @@ export type ChannelSettingsApply = z.infer<
export const channelCredentialSourceSchema = z.enum([
'none',
'encrypted',
'environment'
'environment',
'unreadable'
])
export type ChannelCredentialSource = z.infer<
typeof channelCredentialSourceSchema
@@ -186,12 +188,7 @@ export const channelSettingsSnapshotSchema = z
weixin: weixinChannelSettingsSchema,
wecom: weComChannelSettingsSchema,
dingtalk: dingTalkChannelSettingsSchema,
warning: z
.string()
.trim()
.min(1)
.max(CHANNEL_SETTINGS_LIMITS.maximumWarningLength)
.optional()
warnings: settingsWarningsSchema.optional()
})
.strict()
export type ChannelSettingsSnapshot = z.infer<
+27 -5
View File
@@ -86,6 +86,7 @@ import type {
DocumentParsingSnapshot,
DocumentParsingTestPurpose
} from './document-parsing-contracts'
import type { SettingsWarning } from './settings-warning-contracts'
import type {
KnowledgeChunkDeleteInput,
KnowledgeChunkPage,
@@ -601,7 +602,19 @@ export type ModelConnectionSettings = {
supportsImageInput?: boolean
imageGenerationQuality: ImageGenerationQuality
apiKeyConfigured: boolean
credentialSource: 'none' | 'encrypted' | 'environment'
credentialSource: 'none' | 'encrypted' | 'environment' | 'unreadable'
}
export type ConfiguredRuntimeSettings = {
modelProfiles: ModelConnectionSettings[]
opencodeBaseUrl: string
opencodeBinaryPath: string
opencodeConfigPath: string
continueBinaryPath: string
continueConfigPath: string
workspacePath: string
opencodeModelSource: RuntimeModelSource
continueModelSource: RuntimeModelSource
}
export type RuntimeSettings = {
@@ -625,22 +638,31 @@ export type RuntimeSettings = {
knowledgeEmbeddingBaseUrl: string
knowledgeEmbeddingModel: string
knowledgeEmbeddingApiKeyConfigured: boolean
knowledgeEmbeddingCredentialSource: 'none' | 'encrypted' | 'environment'
knowledgeEmbeddingCredentialSource:
| 'none'
| 'encrypted'
| 'environment'
| 'unreadable'
knowledgeRerankEnabled?: boolean
knowledgeRerankEndpoint?: string
knowledgeRerankModel?: string
knowledgeRerankApiKeyConfigured?: boolean
knowledgeRerankCredentialSource?: 'none' | 'encrypted' | 'environment'
knowledgeRerankCredentialSource?:
| 'none'
| 'encrypted'
| 'environment'
| 'unreadable'
workspacePath: string
apiKeyConfigured: boolean
credentialSource: 'none' | 'encrypted' | 'environment'
credentialSource: 'none' | 'encrypted' | 'environment' | 'unreadable'
modelProfiles: ModelConnectionSettings[]
defaultModelProfileId: string
opencodeModelSource: RuntimeModelSource
continueModelSource: RuntimeModelSource
secureStorageAvailable: boolean
toolApproval: RuntimeSettingsInput['toolApproval']
warning?: string
configured?: ConfiguredRuntimeSettings
warnings?: SettingsWarning[]
}
export type ContextAttachment = ConversationAttachment
+3 -1
View File
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { settingsWarningsSchema } from './settings-warning-contracts'
export const maximumDocumentExtractedCharacters = 5_000_000
export const maximumDocumentOcrSectionCharacters = 1_000_000
@@ -192,7 +193,8 @@ export const documentParsingSnapshotSchema = z
.object({
settings: documentParsingSettingsSchema,
status: documentParsingStatusSchema,
ocrModels: documentOcrModelSnapshotSchema
ocrModels: documentOcrModelSnapshotSchema,
warnings: settingsWarningsSchema.optional()
})
.strict()
+55
View File
@@ -0,0 +1,55 @@
import { z } from 'zod'
export const settingsWarningCodeSchema = z.enum([
'application-settings-recovered',
'document-parsing-settings-recovered',
'capability-settings-recovered',
'runtime-settings-recovered',
'runtime-model-credential-unreadable',
'runtime-model-credential-binding-mismatch',
'runtime-embedding-credential-unreadable',
'runtime-embedding-credential-binding-mismatch',
'runtime-rerank-credential-unreadable',
'runtime-rerank-credential-binding-mismatch',
'channel-settings-recovered',
'channel-weixin-credential-unreadable',
'channel-weixin-secure-storage-unavailable',
'channel-weixin-legacy-binding-invalid',
'channel-wecom-environment-invalid',
'channel-dingtalk-environment-invalid',
'channel-wecom-credential-unreadable',
'channel-dingtalk-credential-unreadable',
'channel-runtime-selections-repaired'
])
export const settingsWarningSchema = z
.object({
code: settingsWarningCodeSchema,
subject: z.string().trim().min(1).max(120).optional(),
count: z.number().int().min(1).max(10_000).optional()
})
.strict()
export const settingsWarningsSchema = z
.array(settingsWarningSchema)
.max(32)
export type SettingsWarningCode = z.infer<
typeof settingsWarningCodeSchema
>
export type SettingsWarning = z.infer<typeof settingsWarningSchema>
export function settingsWarningKey(warning: SettingsWarning): string {
return JSON.stringify([
warning.code,
warning.subject ?? null,
warning.count ?? null
])
}
export function settingsWarningsEqual(
left: SettingsWarning,
right: SettingsWarning
): boolean {
return settingsWarningKey(left) === settingsWarningKey(right)
}