Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a

# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.md
#	apps/cli/README.zh.md
#	docs/user/guide/config.i18n.yaml
#	packages/client/ui-models/src/client/ModelsSection.module.css
#	packages/cordis/repository-plugin/README.i18n.yaml
#	packages/cordis/repository-plugin/README.md
#	packages/cordis/repository-plugin/README.zh.md
#	packages/credentials/README.i18n.yaml
#	packages/credentials/README.md
#	packages/credentials/README.zh.md
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/ui/app-boot/README.i18n.yaml
#	packages/ui/app-boot/README.md
#	packages/ui/app-boot/README.zh.md
#	packages/ui/app-boot/src/index.ts
#	packages/ui/app-boot/tests/config-reload.spec.ts
#	packages/ui/app-boot/tests/personal-config.spec.ts
This commit is contained in:
Yichen Jiang
2026-08-05 18:25:14 +08:00
729 changed files with 7372 additions and 4407 deletions
+57 -27
View File
@@ -10,7 +10,7 @@
import { Context, Service } from 'cordis'
import z from 'schemastery'
import { watch as chokidarWatch } from 'chokidar'
import { mkdir, readFile } from 'node:fs/promises'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, extname, join, resolve } from 'node:path'
import { Document, parseDocument } from 'yaml'
import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
@@ -96,6 +96,11 @@ function isENOENT(error: unknown): boolean {
return (error as NodeJS.ErrnoException | null)?.code === 'ENOENT'
}
/** Whether an exclusive file create found an existing document. */
function isEEXIST(error: unknown): boolean {
return (error as NodeJS.ErrnoException | null)?.code === 'EEXIST'
}
/** File-backed settings provider (`settings.yaml`/`.json`). */
export class SettingsLocal extends Settings {
static Config: z<Config> = z.object({
@@ -139,6 +144,29 @@ export class SettingsLocal extends Settings {
return true
}
/** The resolved YAML/JSON document path exposed to local configuration surfaces. */
override get documentPath(): string {
return this.spec.filename
}
/** Materialize an absent owner-only document, then return its resolved path. */
override prepareDocument(): Promise<string> {
return this.enqueue(async () => {
await mkdir(dirname(this.spec.filename), { recursive: true, mode: 0o700 })
await withFileLock(this.spec.filename, async () => {
try {
await writeFile(this.spec.filename, '', { flag: 'wx', mode: 0o600 })
} catch (error) {
if (isEEXIST(error)) return
throw error
}
this.text = ''
if (!this.isClosed()) this.publish({})
})
return this.spec.filename
})
}
protected async load(): Promise<Record<string, unknown>> {
let text: string
try {
@@ -206,34 +234,36 @@ export class SettingsLocal extends Settings {
// failure: an existing-but-invalid document must fail loud, never be
// silently ignored or overwritten.
yield* super[Service.init]()
if (!this.spec.watch) return
const watcher = chokidarWatch(this.spec.filename, {
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: this.spec.debounceMs,
pollInterval: Math.max(1, Math.min(this.spec.debounceMs, 10)),
},
})
watcher.on('all', () => {
if (this.closed) return
this.queueRefresh()
})
watcher.on('ready', () => {
// The base init's load raced the watcher's own setup: a change written
// between that read and the watcher becoming active never fires an
// event. One reconcile at ready closes the gap.
if (this.closed) return
this.queueRefresh()
})
watcher.on('error', (error) => {
this.ctx.logger.warn('settings-local: watcher error on %s', this.spec.filename)
this.ctx.logger.warn(error)
})
const watcher = this.spec.watch
? chokidarWatch(this.spec.filename, {
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: this.spec.debounceMs,
pollInterval: Math.max(1, Math.min(this.spec.debounceMs, 10)),
},
})
: undefined
if (watcher !== undefined) {
watcher.on('all', () => {
if (this.closed) return
this.queueRefresh()
})
watcher.on('ready', () => {
// The base init's load raced the watcher's own setup: a change written
// between that read and the watcher becoming active never fires an
// event. One reconcile at ready closes the gap.
if (this.closed) return
this.queueRefresh()
})
watcher.on('error', (error) => {
this.ctx.logger.warn('settings-local: watcher error on %s', this.spec.filename)
this.ctx.logger.warn(error)
})
}
yield async () => {
// Quiesce: stop accepting events, close the watcher, then wait out any
// queued or in-flight operation so nothing publishes after disposal.
// Quiesce every operation chain, even when no watcher is configured.
this.closed = true
await watcher.close()
await watcher?.close()
await this.operations
}
}