netxops/src/client/index.ts
oliver f64f1279e7 Fix knowledge-base browse feedback and accept MANIFEST.json paths.
Browse no longer fails silently, falls back to Host directoryPicker RPC, and resolve treats a MANIFEST.json file path as its parent package root.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 11:13:29 +08:00

113 lines
4.1 KiB
TypeScript

/**
* Browser half — Settings → Netx Ops section (uds-auth-style page).
*
* Do not hard-inject `remote.credentials`: shipped `@deepseek-ai/dsh` 0.1.1-rc.2
* remotes assembly does not mount that namespace (Models/Plugins cards only
* inject `remote`). Soft-inject when a newer Host provides it.
*/
import type { Context as ClientContext } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-client-locale/client'
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
import type {} from '@deepseek-ai/dsh-api-remotes/client'
import { NetxopsCard } from './NetxopsCard.tsx'
import { NETXOPS_NS, NetxopsCardController } from './controller.ts'
import { en, zh, type NetxopsLocaleKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
'settings.netxops': NetxopsLocaleKey
}
}
const LOCALE_NS = 'settings.netxops'
/** Match shipped settings shell inject (no remote.credentials). */
export const inject = [
'slots',
'locale',
'remote',
'settingsScope',
]
export function apply(ctx: ClientContext): void {
ctx.effect(() => {
try {
return ctx.locale.register(LOCALE_NS, { zh, en })
} catch {
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
return () => {
offZh?.()
offEn?.()
}
}
}, 'netxops: locales')
const card = new NetxopsCardController(
ctx.settingsScope.bind({ namespace: NETXOPS_NS }),
ctx,
)
const t = ctx.locale.bind(LOCALE_NS) as (key: NetxopsLocaleKey) => string
// Optional: newer remotes that mount credentials unlock the token field.
ctx.inject(['remote.credentials'], (credCtx) => {
card.setCredentialsAvailable(true)
credCtx.effect(() => {
const off = credCtx.remote.$on('credentials/reference-updated', (ref) => {
card.refreshCredential(String(ref))
})
return () => {
off()
card.setCredentialsAvailable(false)
}
}, 'netxops: credential invalidations')
})
// Soft-inject Connection so the card can poll host WSS status + KB status.
ctx.inject(['connection'], (connCtx) => {
const call = connCtx.connection?.rpc?.call?.bind(connCtx.connection.rpc)
if (typeof call !== 'function') {
connCtx.logger.warn('netxops: connection.rpc.call unavailable — alarm status UI disabled')
return
}
card.setAlarmPushRpc(call)
connCtx.effect(() => () => {
card.setAlarmPushRpc(undefined)
}, 'netxops: clear alarm-push rpc')
})
// Soft-inject Host directory picker for knowledge-base browse.
// Prefer the dedicated remote namespace; also accept ctx.remote.directoryPicker.
const bindDirectoryPicker = (picker: { pick?: (signal?: AbortSignal) => Promise<string | null> } | undefined): void => {
if (!picker || typeof picker.pick !== 'function') return
card.setDirectoryPicker(picker as { pick: (signal?: AbortSignal) => Promise<string | null> })
}
bindDirectoryPicker(
(ctx as { remote?: { directoryPicker?: { pick?: (signal?: AbortSignal) => Promise<string | null> } } })
.remote?.directoryPicker,
)
ctx.inject(['remote.directoryPicker'], (dpCtx) => {
const viaGet = typeof (dpCtx as { get?: (name: string) => unknown }).get === 'function'
? (dpCtx as { get: (name: string) => unknown }).get('remote.directoryPicker') as
| { pick?: (signal?: AbortSignal) => Promise<string | null> }
| undefined
: undefined
const viaNested = (dpCtx as { remote?: { directoryPicker?: { pick?: (signal?: AbortSignal) => Promise<string | null> } } })
.remote?.directoryPicker
bindDirectoryPicker(viaGet ?? viaNested)
dpCtx.effect(() => () => {
card.setDirectoryPicker(undefined)
}, 'netxops: clear directory picker')
})
ctx.slots.inject('settings.section', () => ctx.slots.register({
name: 'settings.section',
id: NETXOPS_NS,
order: 24,
label: () => t('title'),
locale: LOCALE_NS,
inject: () => card.inject(),
}, NetxopsCard))
}