mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
cordis 4.0.0-rc.6, plugin-loader, -include, -group, -timer, -hmr, -logger-console, cosmokit 1.8.1, schemastery 3.18.0 — copied from the cordis-workspace checkout, flattened under vendor/, original npm names, private: true. vendor/README.md is the manifest: upstream repos + commit SHAs, local-modification log, sync procedure. Local modification: hmr's locale YAML imports and .i18n() call removed (avoids a runtime YAML import hook we don't vendor).
185 lines
5.2 KiB
TypeScript
185 lines
5.2 KiB
TypeScript
import { Context, Fiber, Inject } from 'cordis'
|
|
import { deepEqual, isNullable } from 'cosmokit'
|
|
import { Loader } from '../index.ts'
|
|
import { EntryGroup } from './group.ts'
|
|
import { EntryTree } from './tree.ts'
|
|
import { evaluate, interpolate } from './utils.ts'
|
|
|
|
/** Serialized plugin entry options stored in loader config files. */
|
|
export interface EntryOptions {
|
|
/** Stable id inside the containing entry tree. */
|
|
id: string
|
|
/** Module specifier imported by the entry tree. */
|
|
name: string
|
|
/** Config passed to the plugin. */
|
|
config?: any
|
|
/** Marks this entry as a nested group. */
|
|
group?: boolean | null
|
|
/** Prevents this entry and descendants from running. */
|
|
disabled?: boolean | null
|
|
/** Required services or service intercept config for this entry. */
|
|
inject?: Inject | null
|
|
}
|
|
|
|
function takeEntries(object: {}, keys: string[]) {
|
|
const result: [string, any][] = []
|
|
for (const key of keys) {
|
|
if (!(key in object)) continue
|
|
result.push([key, object[key]])
|
|
delete object[key]
|
|
}
|
|
return result
|
|
}
|
|
|
|
function sortKeys<T extends {}>(object: T, prepend = ['id', 'name'], append = ['config']): T {
|
|
const part1 = takeEntries(object, prepend)
|
|
const part2 = takeEntries(object, append)
|
|
const rest = takeEntries(object, Object.keys(object)).sort(([a], [b]) => a.localeCompare(b))
|
|
return Object.assign(object, Object.fromEntries([...part1, ...rest, ...part2]))
|
|
}
|
|
|
|
/** One configured plugin node inside an `EntryTree`. */
|
|
export class Entry {
|
|
static readonly key = Symbol.for('cordis.entry')
|
|
|
|
public ctx: Context
|
|
public fiber?: Fiber
|
|
public parent!: EntryGroup
|
|
// safety: call `entry.update()` immediately after creating an entry
|
|
public options = {} as EntryOptions
|
|
public subgroup?: EntryGroup
|
|
public subtree?: EntryTree
|
|
|
|
_initTask?: Promise<void>
|
|
|
|
constructor(public loader: Loader) {
|
|
this.ctx = loader.ctx.extend({ [Entry.key]: this })
|
|
this.context.emit('loader/entry-init', this)
|
|
}
|
|
|
|
get context(): Context {
|
|
return this.ctx
|
|
}
|
|
|
|
get id() {
|
|
let id = this.options.id
|
|
if (this.parent.tree.ctx.fiber.entry) {
|
|
id = this.parent.tree.ctx.fiber.entry.id + EntryTree.sep + id
|
|
}
|
|
return id
|
|
}
|
|
|
|
/** True when this entry or any owning parent entry is disabled. */
|
|
get disabled() {
|
|
// group is always enabled
|
|
if (this.options.group) return false
|
|
let entry: Entry | undefined = this
|
|
do {
|
|
if (entry.options.disabled) return true
|
|
entry = entry.parent.ctx.fiber.entry
|
|
} while (entry)
|
|
return false
|
|
}
|
|
|
|
evaluate(expr: string) {
|
|
return evaluate(this.ctx, expr)
|
|
}
|
|
|
|
_resolveConfig(plugin: any): [any, any?] {
|
|
if (plugin[EntryGroup.key]) return this.options.config
|
|
return interpolate(this.ctx, this.options.config)
|
|
}
|
|
|
|
private _patchContext(diff: string[]) {
|
|
this.context.waterfall('loader/patch-context', this, () => {
|
|
Object.setPrototypeOf(this.ctx, this.parent.ctx)
|
|
|
|
if (this.fiber?.uid && (diff.includes('config') || this.options.group)) {
|
|
this.fiber.update(this._resolveConfig(this.fiber.runtime!.callback), true)
|
|
}
|
|
})
|
|
}
|
|
|
|
async refresh() {
|
|
if (this.fiber) return
|
|
if (this.disabled) return
|
|
await this.init()
|
|
}
|
|
|
|
/** Merge new options, restart as needed, and persist through the parent tree. */
|
|
async update(options: Partial<EntryOptions>, create = false, force = false) {
|
|
const legacy = { ...this.options }
|
|
|
|
// step 1: update options
|
|
if (create) {
|
|
this.options = options as EntryOptions
|
|
} else {
|
|
for (const [key, value] of Object.entries(options)) {
|
|
if (isNullable(value)) {
|
|
delete this.options[key]
|
|
} else {
|
|
this.options[key] = value
|
|
}
|
|
}
|
|
}
|
|
sortKeys(this.options)
|
|
|
|
// step 2: execute
|
|
if (this.disabled) {
|
|
this.fiber?.dispose()
|
|
return
|
|
}
|
|
|
|
// step 3: check if options are changed
|
|
if (this.fiber?.uid) {
|
|
const diff = Object
|
|
.keys({ ...this.options, ...legacy })
|
|
.filter(key => !deepEqual(this.options[key], legacy[key]))
|
|
if (!diff.length && !force) return
|
|
this.context.emit('loader/partial-dispose', this, legacy, true)
|
|
this._patchContext(diff)
|
|
} else {
|
|
await this.init()
|
|
}
|
|
}
|
|
|
|
getOuterStack = () => {
|
|
let entry: Entry | undefined = this
|
|
const result: string[] = []
|
|
do {
|
|
result.push(` at ${entry.parent.tree.ctx.baseUrl}#${entry.options.id}`)
|
|
entry = entry.parent.ctx.fiber.entry
|
|
} while (entry)
|
|
return result
|
|
}
|
|
|
|
/** Import and start the configured plugin if it is not already running. */
|
|
async init() {
|
|
try {
|
|
await (this._initTask ??= this._init())
|
|
} finally {
|
|
this._initTask = undefined
|
|
}
|
|
this.fiber?.await().finally(() => {
|
|
if (this.loader.getTasks().length) return
|
|
this.ctx.reflect.notify(['loader'])
|
|
})
|
|
}
|
|
|
|
private async _init() {
|
|
let exports: any
|
|
try {
|
|
exports = await this.parent.tree.import(this.options.name, this.getOuterStack)
|
|
} catch (error) {
|
|
this.ctx.logger.error(error)
|
|
return
|
|
} finally {
|
|
this._initTask = undefined
|
|
}
|
|
const plugin = this.loader.unwrapExports(exports)
|
|
this._patchContext([])
|
|
this.loader.showLog(this, 'apply')
|
|
this.fiber = this.ctx.registry.plugin(plugin, this._resolveConfig(plugin), this.getOuterStack)
|
|
}
|
|
}
|