mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-11 10:33:29 +08:00
Adapt settings client for DSH 0.2.0 configForms without breaking boot.
Soft-inject configForms/settingsScope and mark Config fields volatile so desktop 0.2.0 no longer waits forever on the removed settingsScope service. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a41a2b53c9
commit
fcc8f75560
6 changed files with 117 additions and 44 deletions
|
|
@ -158,7 +158,9 @@ window.__ModuleLoader__.load({
|
|||
|
||||
const name = 'dsh-ops-cron'
|
||||
const NS = 'dsh-ops-cron'
|
||||
const inject = ['slots', 'locale', 'settingsScope']
|
||||
// Hard-inject only services present on both DSH ≤0.1.5 and ≥0.1.7.
|
||||
// Do NOT hard-inject settingsScope: DSH ≥0.1.7 / 0.2.0 removed it (configForms).
|
||||
const inject = ['slots', 'locale']
|
||||
const API = '/dsh-ops-cron'
|
||||
|
||||
function readCookie(name) {
|
||||
|
|
@ -2222,19 +2224,43 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
|||
}
|
||||
}, 'dsh-ops-cron: locale')
|
||||
|
||||
const scope = ctx.settingsScope.bind({ namespace: NS })
|
||||
const form = createForm(scope)
|
||||
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
||||
name: 'settings.plugin.item',
|
||||
key: NS,
|
||||
locale: LOCALE_NS,
|
||||
inject: () => ({
|
||||
hooks: { card: form.store },
|
||||
edit: (field, value) => form.edit(field, value),
|
||||
save: () => { form.save() },
|
||||
discard: () => form.discard(),
|
||||
}),
|
||||
}, SettingsCard))
|
||||
// Soft-mount Plugins card: configForms (DSH ≥0.1.7 / 0.2.0) or settingsScope (≤0.1.5).
|
||||
let settingsMounted = false
|
||||
const mountSettingsCard = (scope) => {
|
||||
if (settingsMounted || !scope) return
|
||||
settingsMounted = true
|
||||
const form = createForm(scope)
|
||||
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
||||
name: 'settings.plugin.item',
|
||||
key: NS,
|
||||
locale: LOCALE_NS,
|
||||
inject: () => ({
|
||||
hooks: { card: form.store },
|
||||
edit: (field, value) => form.edit(field, value),
|
||||
save: () => { form.save() },
|
||||
discard: () => form.discard(),
|
||||
}),
|
||||
}, SettingsCard))
|
||||
}
|
||||
ctx.inject(['configForms'], (formsCtx) => {
|
||||
const forms = formsCtx.configForms || formsCtx.get?.('configForms')
|
||||
if (!forms || typeof forms.get !== 'function') {
|
||||
formsCtx.logger?.warn?.('[dsh-ops-cron] configForms present but .get missing')
|
||||
return
|
||||
}
|
||||
formsCtx.logger?.info?.('[dsh-ops-cron] settings card via configForms')
|
||||
mountSettingsCard(forms.get(NS))
|
||||
})
|
||||
ctx.inject(['settingsScope'], (scopeCtx) => {
|
||||
if (settingsMounted) return
|
||||
const binder = scopeCtx.settingsScope || scopeCtx.get?.('settingsScope')
|
||||
if (!binder || typeof binder.bind !== 'function') {
|
||||
scopeCtx.logger?.warn?.('[dsh-ops-cron] settingsScope present but .bind missing')
|
||||
return
|
||||
}
|
||||
scopeCtx.logger?.info?.('[dsh-ops-cron] settings card via settingsScope')
|
||||
mountSettingsCard(binder.bind({ namespace: NS }))
|
||||
})
|
||||
|
||||
const faces = {
|
||||
sessions: undefined,
|
||||
|
|
|
|||
1
lib/index.d.ts
vendored
1
lib/index.d.ts
vendored
|
|
@ -10,6 +10,7 @@ export interface Config {
|
|||
historyLimit?: number
|
||||
overlapPolicy?: 'skip'
|
||||
misfirePolicy?: 'skip'
|
||||
allowExternalCwd?: boolean
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config?: Config): void
|
||||
|
|
|
|||
94
lib/index.js
94
lib/index.js
|
|
@ -109,13 +109,22 @@ function loadPkg(id) {
|
|||
const SchemaMod = loadPkg('@deepseek-ai/schemastery')
|
||||
const Schema = SchemaMod.default || SchemaMod
|
||||
|
||||
/**
|
||||
* DSH ≥0.1.7 / 0.2.0 only projects Config fields marked `.volatile()` into the
|
||||
* settings UI. Zero volatile fields → the whole entry is silently filtered.
|
||||
* Older schemastery without `volatile` keeps the bare schema (≤0.1.5 path).
|
||||
*/
|
||||
function vol(schema) {
|
||||
return typeof schema?.volatile === 'function' ? schema.volatile() : schema
|
||||
}
|
||||
|
||||
export const Config = Schema.object({
|
||||
enabled: Schema.boolean().default(DEFAULT_SETTINGS.enabled),
|
||||
timezone: Schema.string().default(DEFAULT_SETTINGS.timezone),
|
||||
historyLimit: Schema.number().min(10).max(2000).step(1).default(DEFAULT_SETTINGS.historyLimit),
|
||||
overlapPolicy: Schema.union(['skip']).default(DEFAULT_SETTINGS.overlapPolicy),
|
||||
misfirePolicy: Schema.union(['skip']).default(DEFAULT_SETTINGS.misfirePolicy),
|
||||
allowExternalCwd: Schema.boolean().default(DEFAULT_SETTINGS.allowExternalCwd),
|
||||
enabled: vol(Schema.boolean().default(DEFAULT_SETTINGS.enabled)),
|
||||
timezone: vol(Schema.string().default(DEFAULT_SETTINGS.timezone)),
|
||||
historyLimit: vol(Schema.number().min(10).max(2000).step(1).default(DEFAULT_SETTINGS.historyLimit)),
|
||||
overlapPolicy: vol(Schema.union(['skip']).default(DEFAULT_SETTINGS.overlapPolicy)),
|
||||
misfirePolicy: vol(Schema.union(['skip']).default(DEFAULT_SETTINGS.misfirePolicy)),
|
||||
allowExternalCwd: vol(Schema.boolean().default(DEFAULT_SETTINGS.allowExternalCwd)),
|
||||
})
|
||||
|
||||
function resolveConfig(config = {}) {
|
||||
|
|
@ -178,8 +187,60 @@ export function apply(ctx, config = {}) {
|
|||
})
|
||||
|
||||
ctx.inject(['settings'], (sctx) => {
|
||||
sctx.settings.register(NS, Config, { base: entry, applies: 'live' })
|
||||
sctx.logger?.info?.(`[dsh-ops-cron] settings namespace "${NS}" registered`)
|
||||
const settings = sctx.settings
|
||||
// ≤0.1.5: explicit namespace registration. ≥0.1.7: Config is projected from
|
||||
// the Loader entry — register may be absent or refuse; follow describe().
|
||||
if (typeof settings?.register === 'function') {
|
||||
try {
|
||||
settings.register(NS, Config, { base: entry, applies: 'live' })
|
||||
sctx.logger?.info?.(`[dsh-ops-cron] settings namespace "${NS}" registered`)
|
||||
} catch (error) {
|
||||
sctx.logger?.warn?.(
|
||||
`[dsh-ops-cron] settings.register failed (will follow describe): ${error instanceof Error ? error.message : error}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const describeRows = () => {
|
||||
try {
|
||||
const raw = typeof settings?.describe === 'function' ? settings.describe() : null
|
||||
if (Array.isArray(raw)) return raw
|
||||
if (raw && typeof raw === 'object' && Array.isArray(raw.namespaces)) return raw.namespaces
|
||||
} catch { /* settling */ }
|
||||
return []
|
||||
}
|
||||
const readLive = () => {
|
||||
const row = describeRows().find((item) => item && item.ns === NS)
|
||||
if (row?.value !== null && typeof row?.value === 'object' && !Array.isArray(row.value)) {
|
||||
return resolveConfig({ ...entry, ...row.value })
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
if (typeof settings?.watch === 'function') {
|
||||
try {
|
||||
const unwatch = settings.watch(NS, (next) => {
|
||||
service.updateSettings(resolveConfig(next)).catch(() => {})
|
||||
})
|
||||
if (typeof unwatch === 'function') {
|
||||
sctx.effect(() => () => unwatch(), 'dsh-ops-cron: settings.watch')
|
||||
}
|
||||
} catch {
|
||||
// watch optional
|
||||
}
|
||||
} else {
|
||||
// ≥0.1.7: no scope.watch — poll describe so UI writes reach the scheduler.
|
||||
let lastFingerprint = JSON.stringify(readLive())
|
||||
const timer = setInterval(() => {
|
||||
const next = readLive()
|
||||
const fingerprint = JSON.stringify(next)
|
||||
if (fingerprint === lastFingerprint) return
|
||||
lastFingerprint = fingerprint
|
||||
service.updateSettings(next).catch(() => {})
|
||||
}, 2_000)
|
||||
sctx.effect(() => () => clearInterval(timer), 'dsh-ops-cron: settings describe poll')
|
||||
sctx.logger?.info?.('[dsh-ops-cron] following settings via describe() (no watch — DSH ≥0.1.7 path)')
|
||||
}
|
||||
})
|
||||
|
||||
ctx.inject(['tools'], (tctx) => {
|
||||
|
|
@ -193,23 +254,6 @@ export function apply(ctx, config = {}) {
|
|||
}
|
||||
})
|
||||
|
||||
ctx.effect(() => {
|
||||
const off = ctx.on('internal/service', (serviceName, value) => {
|
||||
if (serviceName !== 'settings' || !value?.watch) return
|
||||
try {
|
||||
const unwatch = value.watch(NS, (next) => {
|
||||
service.updateSettings(resolveConfig(next)).catch(() => {})
|
||||
})
|
||||
if (typeof unwatch === 'function') {
|
||||
// Replaced when the effect disposes.
|
||||
}
|
||||
} catch {
|
||||
// watch is optional.
|
||||
}
|
||||
})
|
||||
return () => off()
|
||||
}, 'dsh-ops-cron: settings watch')
|
||||
|
||||
ctx.effect(() => registerWebRoute(ctx, (req, res) => {
|
||||
service.handleRequest(req, res).catch(() => {
|
||||
if (!res.headersSent) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue