@dsh-plugin/dsh-loader 1.0.0 → 1.1.0-dev.32287576957

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +160 -0
  2. package/README.md +251 -226
  3. package/{README.zh-CN.md → README.zh_CN.md} +227 -202
  4. package/bin/dshloader.mjs +42 -42
  5. package/cordis.patch.yml +8 -8
  6. package/dist/adapters/dsh-1-x.d.ts +23 -0
  7. package/dist/adapters/dsh-1-x.js +223 -0
  8. package/dist/adapters/dsh-1-x.js.map +1 -0
  9. package/dist/adapters/index.d.ts +22 -0
  10. package/dist/adapters/index.js +57 -0
  11. package/dist/adapters/index.js.map +1 -0
  12. package/dist/api.d.ts +13 -0
  13. package/dist/api.js +46 -0
  14. package/dist/api.js.map +1 -0
  15. package/dist/client.d.ts +157 -0
  16. package/dist/client.js +295 -0
  17. package/dist/client.js.map +1 -0
  18. package/dist/index.d.ts +52 -0
  19. package/dist/index.js +89 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/registry.d.ts +35 -0
  22. package/dist/registry.js +229 -0
  23. package/dist/registry.js.map +1 -0
  24. package/dist/services/services.d.ts +4 -0
  25. package/dist/services/services.js +28 -0
  26. package/dist/services/services.js.map +1 -0
  27. package/dist/services/settings.d.ts +41 -0
  28. package/dist/services/settings.js +161 -0
  29. package/dist/services/settings.js.map +1 -0
  30. package/dist/services/web.d.ts +10 -0
  31. package/dist/services/web.js +63 -0
  32. package/dist/services/web.js.map +1 -0
  33. package/dist/setup.d.ts +32 -0
  34. package/dist/setup.js +119 -0
  35. package/dist/setup.js.map +1 -0
  36. package/dist/types.d.ts +111 -0
  37. package/dist/types.js +12 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/version.d.ts +2 -0
  40. package/{src → dist}/version.js +5 -5
  41. package/dist/version.js.map +1 -0
  42. package/lib/client.js +283 -0
  43. package/lib/client.js.map +1 -0
  44. package/package.json +75 -59
  45. package/src/stable/agent.d.ts +1 -1
  46. package/src/stable/agent.js +2 -2
  47. package/src/stable/llm.d.ts +1 -1
  48. package/src/stable/llm.js +2 -2
  49. package/src/stable/runtime.d.ts +1 -1
  50. package/src/stable/runtime.js +5 -5
  51. package/src/stable/schema-form.d.ts +1 -1
  52. package/src/stable/schema-form.js +2 -2
  53. package/src/stable/settings.d.ts +1 -1
  54. package/src/stable/settings.js +2 -2
  55. package/src/stable/tools.d.ts +1 -1
  56. package/src/stable/tools.js +5 -5
  57. package/src/stable/ui-primitives.d.ts +4 -4
  58. package/src/stable/ui-primitives.js +6 -6
  59. package/src/stable/ui-settings.d.ts +6 -6
  60. package/src/stable/ui-settings.js +4 -4
  61. package/src/stable/ui-slots.d.ts +1 -1
  62. package/src/stable/ui-slots.js +2 -2
  63. package/src/stable/web-react.d.ts +1 -1
  64. package/src/stable/web-react.js +2 -2
  65. package/src/adapters/dsh-1-x.js +0 -236
  66. package/src/adapters/index.js +0 -59
  67. package/src/api.js +0 -56
  68. package/src/client.js +0 -330
  69. package/src/index.js +0 -105
  70. package/src/registry.js +0 -242
  71. package/src/services/services.js +0 -28
  72. package/src/services/settings.js +0 -174
  73. package/src/services/web.js +0 -71
  74. package/src/setup.mjs +0 -125
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["name","dsh1x.supports","dsh1x.name","dsh1x.create","require"],"sources":["../src/version.ts","../src/services/settings.ts","../src/adapters/dsh-1-x.ts","../src/adapters/index.ts","../src/client.ts"],"sourcesContent":["// dshloader version — keep in sync with package.json.\nexport const LOADER_VERSION = '1.0.0';\n\n// Prefix used in every console log / error so users can grep dshloader output.\nexport const LOG_PREFIX = '[dshloader]';\n","// Settings stable API (design.md §3.3.1 / §4.1 / §4.2).\n//\n// Two concerns are deliberately separated:\n// 1. naming/shape differences — handled by proxying to ctx.get('settings').\n// 2. access scope (security) — `exposeAllNamespaces` controls whether\n// `describe` returns only the official browser whitelist namespaces\n// (default, matches official behavior) or every registered namespace.\n//\n// Host-side writes always proxy through to the real settings service: host\n// plugin code is already trusted (it runs in the dsh Node process and could\n// call ctx.get('settings').update directly). The whitelist is a browser-side\n// default-deny boundary owned by dsh-host-apiproxy; the browser path is\n// covered separately by the client fetch interceptor (src/client.ts).\nimport { LOG_PREFIX } from '../version.js';\nimport type { CordisContext, SettingsAPI, SettingsResult, NamespaceView } from '../types.js';\n\n/**\n * Official browser-writable settings namespaces (dsh-host-apiproxy\n * WEB_SETTINGS_NAMESPACES), as documented in dsh-upstream-fixes/README.md.\n * Product namespaces and dynamic model-provider namespaces are not enumerable\n * without dsh internals; callers may extend this set via `extraWhitelist`.\n */\nexport const DEFAULT_WEB_SETTINGS_NAMESPACES = new Set([\n 'agent-loop',\n 'shell',\n 'locale',\n 'permission',\n 'ui-conversation',\n 'ui-theme',\n 'web-search-deepseek',\n]);\n\n/** A raw settings descriptor returned by the real settings service. */\ninterface SettingsDescriptor {\n ns: string;\n schema: any;\n value: any;\n base?: any;\n user?: any;\n applies?: any;\n secrets?: { path: string[]; set?: any }[];\n revision?: any;\n}\n\n/**\n * Shape a raw settings descriptor into the official NamespaceView wire shape\n * (mirrors dsh-upstream-fixes/lib/index.js `namespaceView`).\n */\nexport function toNamespaceView(descriptor: SettingsDescriptor): NamespaceView {\n const view: NamespaceView = {\n ns: String(descriptor.ns),\n schema: descriptor.schema,\n value: descriptor.value,\n ...(descriptor.base === undefined ? {} : { base: descriptor.base }),\n ...(descriptor.user === undefined ? {} : { user: descriptor.user }),\n applies: descriptor.applies,\n secrets: (descriptor.secrets ?? []).map((s) => ({ path: [...s.path], set: s.set })),\n revision: descriptor.revision,\n };\n return view;\n}\n\n/**\n * Map a thrown settings error into a SettingsResult, preserving the official\n * `settings-conflict` / `settings-rejected` classification.\n */\nexport function settingsErrorToResult(error: unknown, ns: string, method: string): SettingsResult {\n const isObject = error !== null && typeof error === 'object';\n const isConflict = isObject && ('expected' in error || 'actual' in error);\n if (isConflict) {\n const err = error as { message?: string; expected?: any; actual?: any };\n return {\n ok: false,\n code: 'settings-conflict',\n message: err.message ?? String(error),\n details: {\n ns,\n ...(err.expected === undefined ? {} : { expected: err.expected }),\n ...(err.actual === undefined ? {} : { actual: err.actual }),\n },\n };\n }\n return {\n ok: false,\n code: 'settings-rejected',\n message: error instanceof Error ? error.message : String(error),\n details: { ns, method },\n };\n}\n\n/**\n * Internal shape used while building the API: includes the private `_write`\n * helper so `update`/`replace`/`mutate` can share one implementation.\n */\ntype SettingsAPIBuilder = SettingsAPI & {\n _write(\n method: 'update' | 'replace' | 'mutate',\n ns: string,\n section: any,\n expectedRevision?: number,\n ): Promise<SettingsResult>;\n};\n\n/**\n * Build the `ctx.dshLoader.settings` stable API.\n */\nexport function createSettingsAPI(opts: {\n ctx: CordisContext;\n exposeAllNamespaces: boolean;\n whitelist?: Set<string>;\n}): SettingsAPI {\n const { ctx, exposeAllNamespaces, whitelist } = opts;\n const allowed = whitelist ?? DEFAULT_WEB_SETTINGS_NAMESPACES;\n\n function getSettings() {\n return ctx.get('settings');\n }\n\n function filterNamespaces(views: NamespaceView[]) {\n if (exposeAllNamespaces) return views;\n return views.filter((v) => allowed.has(String(v.ns)));\n }\n\n const api: SettingsAPIBuilder = {\n exposeAllNamespaces: Boolean(exposeAllNamespaces),\n\n /**\n * Register a settings namespace. Proxies to the real settings service's\n * `register(ns, schema, options)` and returns the owner scope\n * ({ get, watch }) — the same shape the official service returns.\n *\n * Unlike describe/update/replace/mutate, register is NOT filtered by the\n * whitelist: host plugin code is trusted and registering a namespace is\n * a composition-time act, not a browser-facing read/write.\n */\n register(ns, schema, options) {\n const settings = getSettings();\n if (settings === undefined || typeof settings.register !== 'function') {\n console.warn(`${LOG_PREFIX}:settings.register settings service unavailable`);\n return undefined;\n }\n return settings.register(ns, schema, options);\n },\n\n describe(options: { redactSecrets?: boolean } = {}) {\n const settings = getSettings();\n if (settings === undefined || typeof settings.describe !== 'function') {\n return [];\n }\n const redactSecrets = options.redactSecrets !== false;\n const descriptors = settings.describe({ redactSecrets }) as SettingsDescriptor[];\n const views = (descriptors ?? []).map(toNamespaceView);\n return filterNamespaces(views);\n },\n\n async _write(\n method: 'update' | 'replace' | 'mutate',\n ns: string,\n section: any,\n expectedRevision?: number,\n ): Promise<SettingsResult> {\n const settings = getSettings();\n if (settings === undefined) {\n return {\n ok: false,\n code: 'internal',\n message: `${LOG_PREFIX}:settings.${method} settings service unavailable`,\n details: { ns },\n };\n }\n try {\n if (method === 'update') await settings.update(ns, section, expectedRevision);\n else if (method === 'replace') await settings.replace(ns, section, expectedRevision);\n else await settings.mutate(ns, section, expectedRevision);\n } catch (error) {\n return settingsErrorToResult(error, ns, method);\n }\n const descriptor = (settings.describe({ redactSecrets: true }) as SettingsDescriptor[]).find(\n (d) => String(d.ns) === String(ns),\n );\n if (descriptor === undefined) {\n return {\n ok: false,\n code: 'internal',\n message: `${LOG_PREFIX}:settings.${method} namespace disposed after write`,\n details: { ns },\n };\n }\n return { ok: true, value: toNamespaceView(descriptor) };\n },\n\n update(ns, section, expectedRevision) {\n return api._write('update', ns, section, expectedRevision);\n },\n replace(ns, section, expectedRevision) {\n return api._write('replace', ns, section, expectedRevision);\n },\n mutate(ns, ops, expectedRevision) {\n return api._write('mutate', ns, ops, expectedRevision);\n },\n };\n\n return api;\n}\n","// dsh 1.x host adapter (design.md §7.4 / §5.1 / §3.3.1).\n//\n// Absorbs the verified repairs from dsh-upstream-fixes:\n// - httpServer -> webServer single-hop service alias (fix 2).\n// - settings namespace whitelist bypass bridge, gated behind\n// `exposeAllNamespaces` (fix 5). Two independent paths:\n// * host side: ctx.dshLoader.settings.* (handled by services/settings.ts\n// + the bridge routes registered here for the browser fetch path).\n// * client side: fetch interceptor in src/client.ts.\n// - package-name aliasing for host-side CJS require (createRequire /\n// require()) — intercepts Module._resolveFilename so a renamed dsh\n// package resolves to its new name without plugin rebuilds.\n//\n// All registrations go through ctx.reflect.provide / ctx.effect so cordis\n// auto-recycles them when the dshloader fiber unloads (design.md §4.4). No\n// custom dispose() is needed for v1's covered capabilities.\nimport { LOG_PREFIX } from '../version.js';\nimport { toNamespaceView, settingsErrorToResult } from '../services/settings.js';\nimport type { CordisContext, AdapterFactory as Factory, HostAdapterConfig } from '../types.js';\n\n// Covers the real dsh release line (0.1.0-rc.x, verified against\n// dsh-upstream-fixes) and the anticipated 1.x line. The adapter name keeps\n// the design-doc label \"dsh-1-x\"; the behavior is identical across both\n// ranges since dsh-upstream-fixes confirms httpServer->webServer + settings\n// bridge work the same way on 0.1.0-rc.7.\nexport const supports = '>=0.1.0-rc.1 <2.0.0';\nexport const name = 'dsh-1-x';\n\n// Browser-facing bridge prefix; the client fetch interceptor (src/client.ts)\n// must use the same prefix.\nexport const BRIDGE_PREFIX = '/api/dshloader';\n\n// Host-side package-name aliases. Maps stable @dshloader/* names to the\n// real dsh package names for this version. Plugins should require() from\n// the stable names; the Module._resolveFilename hook maps them to the real\n// package. When dsh renames a host package, only this table changes.\n//\n// This intercepts CJS require() (including createRequire) via\n// Module._resolveFilename; ESM static imports are build-time resolved and\n// cannot be intercepted at runtime (use pnpm overrides / tsconfig paths\n// for those).\nexport const hostPackageAliases: Record<string, string> = {\n '@dsh-plugin/dsh-loader/tools': '@deepseek-ai/dsh-tools',\n '@dsh-plugin/dsh-loader/llm': '@deepseek-ai/dsh-llm',\n '@dsh-plugin/dsh-loader/agent': '@deepseek-ai/dsh-agent',\n '@dsh-plugin/dsh-loader/settings': '@deepseek-ai/dsh-settings',\n};\n\n/** Loosely-typed node:module NodeModule for the _resolveFilename hook. */\ninterface NodeModuleLike {\n _resolveFilename?: (...args: any[]) => string;\n}\n\n/**\n * Install a Module._resolveFilename hook that maps old package names to\n * new ones for CJS require() calls. Returns a dispose function that\n * removes the hook.\n */\nexport async function installHostPackageAliases(\n aliases: Record<string, string>,\n): Promise<() => void> {\n const entries = Object.entries(aliases);\n if (entries.length === 0) return () => {};\n const aliasMap = new Map(entries);\n // Lazy-import Module to avoid loading it in browser/test contexts.\n let Module: NodeModuleLike | undefined;\n try {\n const mod = await import('node:module');\n const nodeModule = mod.default ?? mod.Module ?? mod;\n Module = nodeModule as NodeModuleLike;\n } catch {\n return () => {};\n }\n if (!Module || typeof Module._resolveFilename !== 'function') return () => {};\n const original = Module._resolveFilename;\n const hooked = function dshloaderResolve(this: unknown, request: string, parent: unknown, ...rest: unknown[]) {\n const mapped = aliasMap.get(request);\n if (mapped !== undefined) return original.call(this, mapped, parent, ...rest);\n return original.call(this, request, parent, ...rest);\n };\n Module._resolveFilename = hooked;\n return () => {\n if (Module && Module._resolveFilename === hooked) Module._resolveFilename = original;\n };\n}\n\n/**\n * @param ctx cordis context\n * @param config\n */\nexport function create(ctx: CordisContext, config: HostAdapterConfig = {}): HostAdapterType {\n const exposeAllNamespaces = Boolean(config.exposeAllNamespaces);\n const packageAliases = { ...hostPackageAliases, ...(config.hostPackageAliases ?? {}) };\n\n async function apply() {\n // --- service alias: httpServer -> webServer (fix 2) ---\n if (ctx.get('httpServer') === undefined) {\n const webServer = ctx.get('webServer');\n if (webServer !== undefined) {\n ctx.reflect.provide('httpServer', webServer);\n console.log(`${LOG_PREFIX} aliased httpServer -> webServer`);\n }\n } else {\n console.log(`${LOG_PREFIX} httpServer already exists, skip alias`);\n }\n\n // --- host package-name aliases (CJS require interception) ---\n if (Object.keys(packageAliases).length > 0) {\n ctx.effect(\n () => installHostPackageAliases(packageAliases),\n 'dshloader: host package-name aliases',\n );\n console.log(`${LOG_PREFIX} installed host package aliases: ${Object.keys(packageAliases).join(', ')}`);\n }\n\n // --- settings bridge routes for the browser fetch path (fix 5, path 2) ---\n // Only registered when the profile explicitly opts in. The host-side\n // stable API (ctx.dshLoader.settings.*) is unaffected by this gate and\n // always available to host plugin code.\n if (exposeAllNamespaces) {\n ctx.effect(\n () => registerSettingsBridgeRoutes(ctx),\n 'dshloader: settings namespace bridge routes',\n );\n }\n }\n\n function dispose() {\n // No cordis-unaware resources held in v1; effect auto-recycle covers\n // everything registered in apply(). Implemented as a no-op to satisfy\n // the HostAdapter interface and future hot-swap scenarios.\n }\n\n return { supports, name, apply, dispose };\n}\n\ninterface HostAdapterType {\n supports: string;\n name: string;\n apply: () => Promise<void>;\n dispose: () => void;\n}\n\n/**\n * Register host bridge routes that the client fetch interceptor forwards\n * non-whitelisted settings requests to. Mirrors dsh-upstream-fixes/lib/index.js\n * `registerRoutes` (settings section).\n */\nfunction registerSettingsBridgeRoutes(ctx: CordisContext): () => void {\n const webServer = ctx.get('webServer') ?? ctx.get('httpServer');\n if (webServer === undefined || typeof webServer.register !== 'function') return () => {};\n return webServer.register({\n kind: 'prefix',\n path: BRIDGE_PREFIX,\n handler: (req: any, res: any) => {\n const json = (status: number, body: unknown) => {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json');\n res.end(JSON.stringify(body));\n };\n const readBody = () =>\n new Promise<string>((done, fail) => {\n let body = '';\n req.on?.('data', (chunk: Buffer | string) => {\n body += chunk.toString('utf8');\n });\n req.on?.('end', () => done(body));\n req.on?.('error', fail);\n });\n const url = req.url ?? '/';\n const method = (req.method ?? 'GET').toUpperCase();\n const path = url.split('?')[0] ?? '/';\n\n void (async () => {\n try {\n // GET /api/dshloader/settings/describe — full redacted namespace list.\n if (method === 'GET' && (path === `${BRIDGE_PREFIX}/settings/describe` || path === `${BRIDGE_PREFIX}/settings/describe/`)) {\n const settings = ctx.get('settings');\n if (settings === undefined) {\n json(200, { ok: false, message: 'settings service unavailable' });\n return;\n }\n json(200, {\n ok: true,\n namespaces: settings.describe({ redactSecrets: true }).map(toNamespaceView),\n });\n return;\n }\n\n // POST /api/dshloader/settings/{update,mutate,replace} — write seam.\n const writeMatch = /^\\/api\\/dshloader\\/settings\\/(update|mutate|replace)$/.exec(path);\n if (method === 'POST' && writeMatch !== null) {\n const mode = writeMatch[1];\n let parsed: { ns?: unknown; section?: unknown; ops?: unknown; expectedRevision?: number } = {};\n try {\n parsed = JSON.parse(await readBody());\n } catch {\n /* keep {} */\n }\n const ns = typeof parsed.ns === 'string' ? parsed.ns : '';\n if (ns.length === 0) {\n json(200, {\n ok: true,\n result: {\n ok: false,\n code: 'settings-rejected',\n message: 'settings write needs a namespace',\n details: {},\n },\n });\n return;\n }\n const settings = ctx.get('settings');\n const result = await runSettingsWrite(\n settings,\n mode,\n ns,\n mode === 'mutate' ? parsed.ops : parsed.section,\n parsed.expectedRevision,\n );\n json(200, { ok: true, result });\n return;\n }\n\n json(404, { ok: false, message: 'not found' });\n } catch (error) {\n json(500, { ok: false, message: error instanceof Error ? error.message : String(error) });\n }\n })();\n },\n });\n}\n\nasync function runSettingsWrite(\n settings: any,\n mode: string,\n ns: string,\n section: unknown,\n expectedRevision?: number,\n) {\n if (settings === undefined) {\n return { ok: false, code: 'internal', message: 'settings service unavailable', details: {} };\n }\n try {\n if (mode === 'update') await settings.update(ns, section, expectedRevision);\n else if (mode === 'replace') await settings.replace(ns, section, expectedRevision);\n else await settings.mutate(ns, section, expectedRevision);\n } catch (error) {\n return settingsErrorToResult(error, ns, mode);\n }\n const descriptor = settings\n .describe({ redactSecrets: true })\n .find((d: { ns: unknown }) => String(d.ns) === String(ns));\n if (descriptor === undefined) {\n return { ok: false, code: 'internal', message: 'settings namespace disposed after write', details: { ns } };\n }\n return { ok: true, value: toNamespaceView(descriptor) };\n}\n","// Adapter registration (design.md §7.4). Imports every host adapter and\n// registers it on the provided AdapterRegistry. Kept as a function so tests\n// can build a fresh registry per case.\nimport * as dsh1x from './dsh-1-x.js';\nimport type { AdapterFactory } from '../types.js';\nimport type { AdapterRegistry } from '../registry.js';\n\n/** All host adapter factories, in registration order. */\nexport const hostAdapters: AdapterFactory[] = [\n { supports: dsh1x.supports, name: dsh1x.name, create: dsh1x.create },\n];\n\n/** Register every built-in host adapter onto a registry. */\nexport function registerHostAdapters(registry: AdapterRegistry): AdapterRegistry {\n for (const factory of hostAdapters) registry.register(factory);\n return registry;\n}\n\n// Client adapter metadata for dsh 1.x (consumed by src/client.ts).\n//\n// packageAliases serves TWO roles:\n// 1. **Stable name → real name** (primary): plugins import from stable\n// names like '@dshloader/ui-primitives' and the adapter maps them to\n// the real dsh package name for this version. When dsh renames a\n// package, only the adapter changes — plugin source and bundle stay\n// the same.\n// 2. **Old real name → new real name** (fallback): if a plugin bundle\n// was built before adopting stable names and still has\n// `require('@deepseek-ai/dsh-client-ui-primitives')` baked in, the\n// adapter can map the old real name to the new real name as a\n// transition measure.\nexport const clientAdapters = [\n {\n supports: dsh1x.supports,\n name: dsh1x.name,\n moduleAliases: {\n // deep source import that breaks when dsh ships no `src/` (fix 1).\n '@deepseek-ai/dsh-client-runtime/src/client/sessions/context-provenance.ts':\n '@deepseek-ai/dsh-client-runtime/client',\n // stable module name (design.md §3.3.3).\n 'dsh/runtime/context-provenance': '@deepseek-ai/dsh-client-runtime/client',\n },\n // Stable package names → real dsh package names for dsh 1.x.\n // Plugins import from @dsh-plugin/dsh-loader/* subpaths (e.g.\n // '@dsh-plugin/dsh-loader/ui-primitives'); the __ModuleLoader__\n // wrapper (installed by installClient) maps them to the real dsh\n // package before hitting the module table. When dsh renames a\n // package, only this table changes — plugin source and bundle stay\n // the same.\n packageAliases: {\n // Client UI component libraries\n '@dsh-plugin/dsh-loader/ui-primitives': '@deepseek-ai/dsh-client-ui-primitives',\n '@dsh-plugin/dsh-loader/ui-slots': '@deepseek-ai/dsh-client-ui-slots',\n '@dsh-plugin/dsh-loader/web-react': '@deepseek-ai/dsh-client-web-react',\n '@dsh-plugin/dsh-loader/schema-form': '@deepseek-ai/dsh-client-schema-form',\n '@dsh-plugin/dsh-loader/ui-settings': '@deepseek-ai/dsh-client-ui-settings/client',\n // Client runtime\n '@dsh-plugin/dsh-loader/runtime': '@deepseek-ai/dsh-client-runtime/client',\n },\n },\n];\n","// dshloader client bundle entry (design.md §3.5 / §4.3 / §7.3).\n//\n// Loaded in the `immediately` prefetch tier (package.json `dsh.client`).\n// Responsibilities:\n// 1. Mount `window.__dshLoader__` with `require` / `registerModuleAlias` /\n// `rpc.settings.*`.\n// 2. Register module-alias factories via `window.__ModuleLoader__.load` so\n// deep source imports (fix 1) and stable module names resolve to the\n// public runtime client entry.\n// 3. When `exposeAllNamespaces` is on, install a fetch interceptor that\n// merges non-whitelisted namespaces into `/api/settings.describe` and\n// routes non-whitelisted writes through the host bridge, echoing the\n// request `rpcId` in the `{ type: 'server-response', rpcId, result }`\n// envelope (fix 5, path 2 — mirrors dsh-upstream-fixes/lib/client.js).\n//\n// The module exports an `installClient` function for testability and runs an\n// IIFE at the bottom for the real browser bundle.\nimport { LOADER_VERSION, LOG_PREFIX } from './version.js';\nimport { clientAdapters } from './adapters/index.js';\nimport { BRIDGE_PREFIX } from './adapters/dsh-1-x.js';\n\n/** Browser-only globals dshloader relies on (typed loosely so the module compiles without DOM lib). */\ntype FetchImpl = (input: any, init?: any) => Promise<{ clone(): { json(): any }; json(): any }>;\ninterface DshLoaderWindowLike {\n fetch: FetchImpl;\n location?: { origin?: string };\n Response?: new (body?: string, init?: { status?: number; headers?: Record<string, string> }) => any;\n __ModuleLoader__?: {\n load(handoff: { id: string; factory: (require: (spec: string) => any) => any }): unknown;\n };\n __DSHLOADER_VERSION__?: string;\n __DSHLOADER_CONFIG__?: { exposeAllNamespaces?: boolean };\n __dshNativeRequire__?: (spec: string) => any;\n __dshLoader__?: unknown;\n}\n\ndeclare global {\n // Minimal browser global used only when this module is run in the browser\n // bundle (the host build never touches it). Declared here so the module\n // type-checks without pulling in the full DOM lib.\n const window: DshLoaderWindowLike;\n}\n\nexport class ModuleNotFoundError extends Error {\n specifier: string;\n\n constructor(specifier: string) {\n super(`${LOG_PREFIX} module not found: ${specifier}`);\n this.name = 'ModuleNotFoundError';\n this.specifier = specifier;\n }\n}\n\ninterface ClientAdapter {\n supports: string;\n name: string;\n moduleAliases?: Record<string, string>;\n packageAliases?: Record<string, string>;\n}\n\n/**\n * Pick the client adapter for the given dsh version. v1 ships a single\n * client adapter (dsh 1.x); when more are added, swap this for a semver-based\n * selection mirroring the host AdapterRegistry.\n */\nfunction pickClientAdapter(dshVersion?: string): ClientAdapter {\n return clientAdapters[0];\n}\n\ninterface CreateClientAPIOpts {\n dshVersion?: string;\n adapterVersion?: string;\n moduleAliases?: Record<string, string>;\n packageAliases?: Map<string, string> | Record<string, string>;\n requireImpl?: (spec: string) => any;\n fetchBridge?: {\n describe: () => Promise<any>;\n write: (mode: string, payload: object) => Promise<any>;\n };\n clientCtx?: { get?: (name: string) => any };\n}\n\n/**\n * Build the `window.__dshLoader__` API object.\n */\nexport function createClientAPI(opts: CreateClientAPIOpts = {}) {\n const aliases = new Map(Object.entries(opts.moduleAliases ?? {}));\n const requireImpl = opts.requireImpl ?? ((spec: string) => {\n throw new ModuleNotFoundError(spec);\n });\n const clientCtx = opts.clientCtx;\n // Reuse the Map from installClient when provided so registerPackageAlias\n // mutations are visible to the __ModuleLoader__ wrapper. Otherwise create\n // a fresh Map from a plain object.\n const packageAliases = opts.packageAliases instanceof Map\n ? opts.packageAliases\n : new Map(Object.entries(opts.packageAliases ?? {}));\n\n const api = {\n version: LOADER_VERSION,\n dshVersion: opts.dshVersion,\n adapterVersion: opts.adapterVersion,\n\n require(specifier: string) {\n if (typeof specifier !== 'string') throw new ModuleNotFoundError(String(specifier));\n const target = aliases.get(specifier);\n if (target === undefined) {\n throw new ModuleNotFoundError(specifier);\n }\n return requireImpl(target);\n },\n\n registerModuleAlias(alias: string, target: string) {\n aliases.set(alias, target);\n },\n\n /**\n * Register a package-name alias for the client module loader's\n * require(). When a plugin's bundle calls require('@old/pkg-name'),\n * the loader's wrapped require remaps it to '@new/pkg-name' before\n * hitting the module table. This lets dsh rename client packages\n * across versions without forcing plugin bundles to rebuild.\n */\n registerPackageAlias(oldName: string, newName: string) {\n packageAliases.set(oldName, newName);\n },\n\n /**\n * Read a client-side cordis service by name.\n * Proxies to `clientCtx.get(name)` — the same ctx.get plugins use\n * inside their client `apply(ctx)`, but exposed through the stable\n * dshloader surface so plugins don't depend on the cordis context\n * shape directly.\n *\n * Only available when dshloader's client apply() received a ctx\n * (cordis client boot). Returns undefined when ctx is not wired.\n */\n services: {\n get(name: string) {\n if (clientCtx === undefined || typeof clientCtx.get !== 'function') return undefined;\n return clientCtx.get(name);\n },\n },\n\n rpc: opts.fetchBridge\n ? {\n settings: {\n describe: () => opts.fetchBridge!.describe(),\n update: (ns: string, section: unknown) => opts.fetchBridge!.write('update', { ns, section }),\n replace: (ns: string, section: unknown) => opts.fetchBridge!.write('replace', { ns, section }),\n mutate: (ns: string, ops: unknown) => opts.fetchBridge!.write('mutate', { ns, ops }),\n },\n }\n : undefined,\n };\n\n return api;\n}\n\ninterface InstallClientOpts {\n window?: DshLoaderWindowLike;\n dshVersion?: string;\n exposeAllNamespaces?: boolean;\n requireImpl?: (spec: string) => any;\n hostBridgePrefix?: string;\n packageAliases?: Record<string, string>;\n clientCtx?: { get?: (name: string) => any };\n}\n\n/**\n * Install dshloader into a browser-like environment.\n */\nexport function installClient(opts: InstallClientOpts = {}) {\n const win = opts.window ?? (typeof window !== 'undefined' ? (window as DshLoaderWindowLike) : undefined);\n if (win === undefined) return undefined;\n\n const dshVersion = opts.dshVersion ?? win.__DSHLOADER_VERSION__;\n const adapter = pickClientAdapter(dshVersion);\n const moduleAliases = { ...(adapter.moduleAliases ?? {}) };\n // Merge adapter-declared package aliases with any passed via opts (opts\n // win on conflict, so tests / runtime can override adapter defaults).\n const packageAliases = new Map(Object.entries(adapter.packageAliases ?? {}));\n if (opts.packageAliases) {\n for (const [k, v] of Object.entries(opts.packageAliases)) packageAliases.set(k, v);\n }\n\n // fetch bridge (only meaningful when exposeAllNamespaces is on).\n const exposeAllNamespaces = Boolean(\n opts.exposeAllNamespaces ?? win.__DSHLOADER_CONFIG__?.exposeAllNamespaces,\n );\n const bridgePrefix = opts.hostBridgePrefix ?? BRIDGE_PREFIX;\n\n const fetchBridge = exposeAllNamespaces\n ? {\n describe: () =>\n win.fetch(`${bridgePrefix}/settings/describe`, { headers: { accept: 'application/json' } }).then((r) => r.json()),\n write: (mode: string, payload: object) =>\n win.fetch(`${bridgePrefix}/settings/${mode}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }).then((r) => r.json()),\n }\n : undefined;\n\n const api = createClientAPI({\n dshVersion,\n adapterVersion: adapter.supports,\n moduleAliases,\n packageAliases,\n requireImpl: opts.requireImpl ?? ((spec) => win.__dshNativeRequire__?.(spec)),\n fetchBridge,\n clientCtx: opts.clientCtx,\n });\n win.__dshLoader__ = api;\n\n // Wrap __ModuleLoader__.load so every factory's require() function\n // applies package-name aliases before hitting the module table. This\n // must happen BEFORE registering module-alias factories below, because\n // those factories also receive the wrapped require.\n const loader = win.__ModuleLoader__;\n if (loader && typeof loader.load === 'function') {\n const originalLoad = loader.load.bind(loader);\n loader.load = function dshloaderLoad(handoff: { id: string; factory: (require: (spec: string) => any) => any }) {\n const wrappedFactory = (require: (spec: string) => any) => {\n const aliasedRequire = (spec: string) => {\n const mapped = packageAliases.get(spec);\n return require(mapped ?? spec);\n };\n return handoff.factory(aliasedRequire);\n };\n return originalLoad({ id: handoff.id, factory: wrappedFactory });\n };\n }\n\n // Register module-alias factories with the client module loader (fix 1).\n if (loader && typeof loader.load === 'function') {\n for (const [aliasId, target] of Object.entries(moduleAliases)) {\n loader.load({\n id: aliasId,\n factory: (require: (spec: string) => any) => {\n const mod = require(target);\n // Preserve the deep import's expected named export shape.\n if (aliasId.endsWith('context-provenance.ts') || aliasId.endsWith('context-provenance')) {\n return { contextProvenance: mod.contextProvenance };\n }\n return mod;\n },\n });\n }\n }\n\n if (exposeAllNamespaces) {\n installSettingsFetchInterceptor(win, bridgePrefix);\n console.warn(`${LOG_PREFIX} exposeAllNamespaces enabled: bypassing official settings whitelist`);\n }\n\n return api;\n}\n\n/**\n * Fetch interceptor for settings namespace whitelist bypass (fix 5, path 2).\n * Mirrors dsh-upstream-fixes/lib/client.js (127-223).\n *\n * - /api/settings.describe: merge non-whitelisted namespaces from the host\n * bridge into the official response (rpcId/official fields preserved).\n * - /api/settings.{update,mutate,replace}: route writes for namespaces the\n * official proxy does NOT expose through the host bridge, and rebuild the\n * response envelope as `{ type: 'server-response', rpcId, result }` so the\n * official client can correlate the response with the request.\n */\nexport function installSettingsFetchInterceptor(\n win: DshLoaderWindowLike,\n bridgePrefix = BRIDGE_PREFIX,\n): () => void {\n if (typeof win.fetch !== 'function') return () => {};\n const originalFetch = win.fetch;\n /** Namespaces the official proxy itself exposed (learned from describe). */\n const officialExposed = new Set<string>();\n\n win.fetch = async function dshloaderFetch(this: unknown, input: any, init?: any) {\n let pathname = '';\n try {\n const urlLike = typeof input === 'string' ? input : (input as { url?: string }).url;\n pathname = new URL(urlLike as string, win.location?.origin ?? 'http://localhost').pathname;\n } catch {\n pathname = String(input).split('?')[0];\n }\n const method = (init?.method ?? 'GET').toUpperCase();\n\n // describe: merge bridge namespaces into the official response.\n if (pathname === '/api/settings.describe') {\n const response = await originalFetch.call(this, input, init);\n try {\n const body = await response.clone().json();\n const namespaces = body?.result?.value?.namespaces;\n if (!Array.isArray(namespaces)) return response;\n for (const row of namespaces) {\n if (typeof row?.ns === 'string') officialExposed.add(row.ns);\n }\n const extra = await originalFetch.call(this, `${bridgePrefix}/settings/describe`, {\n headers: { accept: 'application/json' },\n });\n const extraBody = await extra.json();\n if (extraBody?.ok !== true || !Array.isArray(extraBody.namespaces)) return response;\n const seen = new Set(namespaces.map((row: { ns: string }) => row.ns));\n const merged = [...namespaces, ...extraBody.namespaces.filter((row: { ns: string }) => !seen.has(row.ns))];\n const ResponseCtor = win.Response!;\n return new ResponseCtor(\n JSON.stringify({ ...body, result: { ...body.result, value: { ...body.result.value, namespaces: merged } } }),\n { status: 200, headers: { 'content-type': 'application/json' } },\n );\n } catch {\n return response;\n }\n }\n\n // writes: route non-whitelisted namespaces through the bridge, echo rpcId.\n if (\n (pathname === '/api/settings.update' || pathname === '/api/settings.mutate' || pathname === '/api/settings.replace') &&\n method === 'POST'\n ) {\n let rpcId: unknown = null;\n let payload: { ns?: string; [k: string]: unknown } | null = null;\n try {\n const parsed = JSON.parse(String(init?.body ?? '{}'));\n rpcId = parsed.rpcId;\n payload = parsed.payload;\n } catch {\n /* fall through to the original endpoint */\n }\n const ns = typeof payload?.ns === 'string' ? payload.ns : '';\n if (rpcId !== null && ns !== '' && officialExposed.size > 0 && !officialExposed.has(ns)) {\n try {\n const mode = pathname.slice('/api/settings.'.length);\n const res = await originalFetch.call(this, `${bridgePrefix}/settings/${mode}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n });\n const body = await res.json();\n if (body?.result) {\n const ResponseCtor = win.Response!;\n return new ResponseCtor(\n JSON.stringify({ type: 'server-response', rpcId, result: body.result }),\n { status: 200, headers: { 'content-type': 'application/json' } },\n );\n }\n } catch {\n /* fall back to the original endpoint */\n }\n }\n }\n\n return originalFetch.call(this, input, init);\n };\n\n return () => {\n win.fetch = originalFetch;\n };\n}\n\n// ── cordis client-plugin entry ─────────────────────────────────────────\n// dsh's client boot applies every registered bundle as a cordis plugin:\n// the module must expose `apply` (third-party plugins / dsh-client-runtime\n// all do `exports.apply = apply`). Without it the client boot fails with\n// \"invalid plugin, expect function or object with an \\\"apply\\\" method\".\n// `installClient` mounts window.__dshLoader__, registers module aliases,\n// wires the client cordis ctx for `services.get`, and (when opted in)\n// installs the settings fetch interceptor.\nexport const name = '@dsh-plugin/dsh-loader'\nexport const inject: string[] = []\nexport function apply(ctx: { get?: (name: string) => any }) {\n if (typeof window !== 'undefined') installClient({ window: window as DshLoaderWindowLike, clientCtx: ctx });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,59 +1,75 @@
1
- {
2
- "name": "@dsh-plugin/dsh-loader",
3
- "version": "1.0.0",
4
- "description": "Runtime compatibility shim for dsh (DeepSeek Harness) cordis bundle plugins: decouples third-party plugins from real dsh internal service names, module paths, and RPC details via a version-aware adapter registry.",
5
- "type": "module",
6
- "main": "src/index.js",
7
- "exports": {
8
- ".": "./src/index.js",
9
- "./client": "./lib/client.js",
10
- "./registry": "./src/registry.js",
11
- "./cordis.patch.yml": "./cordis.patch.yml",
12
- "./package.json": "./package.json",
13
- "./ui-primitives": "./src/stable/ui-primitives.js",
14
- "./ui-settings": "./src/stable/ui-settings.js",
15
- "./ui-slots": "./src/stable/ui-slots.js",
16
- "./web-react": "./src/stable/web-react.js",
17
- "./schema-form": "./src/stable/schema-form.js",
18
- "./runtime": "./src/stable/runtime.js",
19
- "./tools": "./src/stable/tools.js",
20
- "./llm": "./src/stable/llm.js",
21
- "./agent": "./src/stable/agent.js",
22
- "./settings": "./src/stable/settings.js"
23
- },
24
- "bin": {
25
- "dshloader": "./bin/dshloader.mjs"
26
- },
27
- "files": [
28
- "src",
29
- "bin",
30
- "lib/client.js",
31
- "lib/client.js.map",
32
- "cordis.patch.yml",
33
- "README.md"
34
- ],
35
- "scripts": {
36
- "build:client": "tsdown --config tsdown.client.config.mjs",
37
- "test": "node --test \"tests/**/*.test.mjs\"",
38
- "test:l1": "node --test \"tests/*.test.mjs\"",
39
- "test:l2": "node --test \"tests/module/*.test.mjs\"",
40
- "test:l3": "node --test \"tests/integration/*.test.mjs\"",
41
- "setup": "node bin/dshloader.mjs setup"
42
- },
43
- "engines": {
44
- "node": ">=18"
45
- },
46
- "dependencies": {
47
- "semver": "^7.8.5"
48
- },
49
- "dsh": {
50
- "bundle": {
51
- "patch": "./cordis.patch.yml"
52
- },
53
- "client": {
54
- "platform": "web",
55
- "immediately": true
56
- }
57
- },
58
- "license": "BSD-3-Clause"
59
- }
1
+ {
2
+ "name": "@dsh-plugin/dsh-loader",
3
+ "version": "1.1.0-dev.32287576957",
4
+ "description": "Runtime compatibility shim for dsh (DeepSeek Harness) cordis bundle plugins: decouples third-party plugins from real dsh internal service names, module paths, and RPC details via a version-aware adapter registry.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/dsh-plugins/dsh-loader"
9
+ },
10
+ "main": "dist/index.js",
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./client": "./lib/client.js",
14
+ "./registry": "./dist/registry.js",
15
+ "./cordis.patch.yml": "./cordis.patch.yml",
16
+ "./package.json": "./package.json",
17
+ "./ui-primitives": "./src/stable/ui-primitives.js",
18
+ "./ui-settings": "./src/stable/ui-settings.js",
19
+ "./ui-slots": "./src/stable/ui-slots.js",
20
+ "./web-react": "./src/stable/web-react.js",
21
+ "./schema-form": "./src/stable/schema-form.js",
22
+ "./runtime": "./src/stable/runtime.js",
23
+ "./tools": "./src/stable/tools.js",
24
+ "./llm": "./src/stable/llm.js",
25
+ "./agent": "./src/stable/agent.js",
26
+ "./settings": "./src/stable/settings.js"
27
+ },
28
+ "bin": {
29
+ "dshloader": "./bin/dshloader.mjs"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "src/stable",
34
+ "bin",
35
+ "lib/client.js",
36
+ "lib/client.js.map",
37
+ "cordis.patch.yml",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json && tsdown --config tsdown.client.config.mjs",
43
+ "build:host": "tsc -p tsconfig.build.json",
44
+ "build:client": "tsdown --config tsdown.client.config.mjs",
45
+ "typecheck": "tsc --noEmit",
46
+ "pretest": "npm run build:host",
47
+ "test": "node --test \"tests/**/*.test.mjs\"",
48
+ "test:l1": "node --test \"tests/*.test.mjs\"",
49
+ "test:l2": "node --test \"tests/module/*.test.mjs\"",
50
+ "test:l3": "node --test \"tests/integration/*.test.mjs\"",
51
+ "setup": "node bin/dshloader.mjs setup"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "dependencies": {
57
+ "semver": "^7.8.5"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^26.2.0",
61
+ "@types/semver": "^7.8.0",
62
+ "tsdown": "^0.22.2",
63
+ "typescript": "^7.0.2"
64
+ },
65
+ "dsh": {
66
+ "bundle": {
67
+ "patch": "./cordis.patch.yml"
68
+ },
69
+ "client": {
70
+ "platform": "web",
71
+ "immediately": true
72
+ }
73
+ },
74
+ "license": "LGPL-3.0-only"
75
+ }
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-agent';
1
+ export * from '@deepseek-ai/dsh-agent';
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/agent
2
- export * from '@deepseek-ai/dsh-agent';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/agent
2
+ export * from '@deepseek-ai/dsh-agent';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-llm';
1
+ export * from '@deepseek-ai/dsh-llm';
package/src/stable/llm.js CHANGED
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/llm
2
- export * from '@deepseek-ai/dsh-llm';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/llm
2
+ export * from '@deepseek-ai/dsh-llm';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-client-runtime/client';
1
+ export * from '@deepseek-ai/dsh-client-runtime/client';
@@ -1,5 +1,5 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/runtime
2
- //
3
- // Maps to @deepseek-ai/dsh-client-runtime/client — the public client
4
- // entry point of the dsh client runtime.
5
- export * from '@deepseek-ai/dsh-client-runtime/client';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/runtime
2
+ //
3
+ // Maps to @deepseek-ai/dsh-client-runtime/client — the public client
4
+ // entry point of the dsh client runtime.
5
+ export * from '@deepseek-ai/dsh-client-runtime/client';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-client-schema-form';
1
+ export * from '@deepseek-ai/dsh-client-schema-form';
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/schema-form
2
- export * from '@deepseek-ai/dsh-client-schema-form';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/schema-form
2
+ export * from '@deepseek-ai/dsh-client-schema-form';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-settings';
1
+ export * from '@deepseek-ai/dsh-settings';
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/settings
2
- export * from '@deepseek-ai/dsh-settings';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/settings
2
+ export * from '@deepseek-ai/dsh-settings';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-tools';
1
+ export * from '@deepseek-ai/dsh-tools';
@@ -1,5 +1,5 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/tools
2
- //
3
- // Host-side tools API. Plugins import from this subpath instead of
4
- // directly from @deepseek-ai/dsh-tools.
5
- export * from '@deepseek-ai/dsh-tools';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/tools
2
+ //
3
+ // Host-side tools API. Plugins import from this subpath instead of
4
+ // directly from @deepseek-ai/dsh-tools.
5
+ export * from '@deepseek-ai/dsh-tools';
@@ -1,4 +1,4 @@
1
- // Stable subpath types: @dsh-plugin/dsh-loader/ui-primitives
2
- // Re-export the real package's types so TS consumers (and this plugin's
3
- // client bundle) stay typed without importing @deepseek-ai/* directly.
4
- export * from '@deepseek-ai/dsh-client-ui-primitives';
1
+ // Stable subpath types: @dsh-plugin/dsh-loader/ui-primitives
2
+ // Re-export the real package's types so TS consumers (and this plugin's
3
+ // client bundle) stay typed without importing @deepseek-ai/* directly.
4
+ export * from '@deepseek-ai/dsh-client-ui-primitives';
@@ -1,6 +1,6 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/ui-primitives
2
- //
3
- // Plugins import UI primitives from this subpath instead of directly
4
- // from @deepseek-ai/dsh-client-ui-primitives. When dsh renames the
5
- // package, only this file (and the adapter's packageAliases) change.
6
- export * from '@deepseek-ai/dsh-client-ui-primitives';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/ui-primitives
2
+ //
3
+ // Plugins import UI primitives from this subpath instead of directly
4
+ // from @deepseek-ai/dsh-client-ui-primitives. When dsh renames the
5
+ // package, only this file (and the adapter's packageAliases) change.
6
+ export * from '@deepseek-ai/dsh-client-ui-primitives';
@@ -1,6 +1,6 @@
1
- // Stable subpath types: @dsh-plugin/dsh-loader/ui-settings
2
- // Side-effect import triggers the dsh-client-ui-settings/client SlotMap
3
- // merge (adds 'settings.section') so PropsRuntime<'settings.section'>
4
- // stays typed. Re-export keeps values/types reachable.
5
- import '@deepseek-ai/dsh-client-ui-settings/client';
6
- export * from '@deepseek-ai/dsh-client-ui-settings/client';
1
+ // Stable subpath types: @dsh-plugin/dsh-loader/ui-settings
2
+ // Side-effect import triggers the dsh-client-ui-settings/client SlotMap
3
+ // merge (adds 'settings.section') so PropsRuntime<'settings.section'>
4
+ // stays typed. Re-export keeps values/types reachable.
5
+ import '@deepseek-ai/dsh-client-ui-settings/client';
6
+ export * from '@deepseek-ai/dsh-client-ui-settings/client';
@@ -1,4 +1,4 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/ui-settings
2
- // Maps to dsh-client-ui-settings/client — the browser entry that owns the
3
- // settings.section SlotMap contract (see ui-settings.d.ts).
4
- export * from '@deepseek-ai/dsh-client-ui-settings/client';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/ui-settings
2
+ // Maps to dsh-client-ui-settings/client — the browser entry that owns the
3
+ // settings.section SlotMap contract (see ui-settings.d.ts).
4
+ export * from '@deepseek-ai/dsh-client-ui-settings/client';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-client-ui-slots';
1
+ export * from '@deepseek-ai/dsh-client-ui-slots';
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/ui-slots
2
- export * from '@deepseek-ai/dsh-client-ui-slots';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/ui-slots
2
+ export * from '@deepseek-ai/dsh-client-ui-slots';
@@ -1 +1 @@
1
- export * from '@deepseek-ai/dsh-client-web-react';
1
+ export * from '@deepseek-ai/dsh-client-web-react';
@@ -1,2 +1,2 @@
1
- // Stable re-export: @dsh-plugin/dsh-loader/web-react
2
- export * from '@deepseek-ai/dsh-client-web-react';
1
+ // Stable re-export: @dsh-plugin/dsh-loader/web-react
2
+ export * from '@deepseek-ai/dsh-client-web-react';
@@ -1,236 +0,0 @@
1
- // dsh 1.x host adapter (design.md §7.4 / §5.1 / §3.3.1).
2
- //
3
- // Absorbs the verified repairs from dsh-upstream-fixes:
4
- // - httpServer -> webServer single-hop service alias (fix 2).
5
- // - settings namespace whitelist bypass bridge, gated behind
6
- // `exposeAllNamespaces` (fix 5). Two independent paths:
7
- // * host side: ctx.dshLoader.settings.* (handled by services/settings.js
8
- // + the bridge routes registered here for the browser fetch path).
9
- // * client side: fetch interceptor in src/client.js.
10
- // - package-name aliasing for host-side CJS require (createRequire /
11
- // require()) — intercepts Module._resolveFilename so a renamed dsh
12
- // package resolves to its new name without plugin rebuilds.
13
- //
14
- // All registrations go through ctx.reflect.provide / ctx.effect so cordis
15
- // auto-recycles them when the dshloader fiber unloads (design.md §4.4). No
16
- // custom dispose() is needed for v1's covered capabilities.
17
- import { LOG_PREFIX } from '../version.js';
18
- import { toNamespaceView, settingsErrorToResult } from '../services/settings.js';
19
-
20
- // Covers the real dsh release line (0.1.0-rc.x, verified against
21
- // dsh-upstream-fixes) and the anticipated 1.x line. The adapter name keeps
22
- // the design-doc label "dsh-1-x"; the behavior is identical across both
23
- // ranges since dsh-upstream-fixes confirms httpServer->webServer + settings
24
- // bridge work the same way on 0.1.0-rc.7.
25
- export const supports = '>=0.1.0-rc.1 <2.0.0';
26
- export const name = 'dsh-1-x';
27
-
28
- // Browser-facing bridge prefix; the client fetch interceptor (src/client.js)
29
- // must use the same prefix.
30
- export const BRIDGE_PREFIX = '/api/dshloader';
31
-
32
- // Host-side package-name aliases. Maps stable @dshloader/* names to the
33
- // real dsh package names for this version. Plugins should require() from
34
- // the stable names; the Module._resolveFilename hook maps them to the real
35
- // package. When dsh renames a host package, only this table changes.
36
- //
37
- // This intercepts CJS require() (including createRequire) via
38
- // Module._resolveFilename; ESM static imports are build-time resolved and
39
- // cannot be intercepted at runtime (use pnpm overrides / tsconfig paths
40
- // for those).
41
- export const hostPackageAliases = {
42
- '@dsh-plugin/dsh-loader/tools': '@deepseek-ai/dsh-tools',
43
- '@dsh-plugin/dsh-loader/llm': '@deepseek-ai/dsh-llm',
44
- '@dsh-plugin/dsh-loader/agent': '@deepseek-ai/dsh-agent',
45
- '@dsh-plugin/dsh-loader/settings': '@deepseek-ai/dsh-settings',
46
- };
47
-
48
- /**
49
- * Install a Module._resolveFilename hook that maps old package names to
50
- * new ones for CJS require() calls. Returns a dispose function that
51
- * removes the hook.
52
- */
53
- export async function installHostPackageAliases(aliases) {
54
- const entries = Object.entries(aliases);
55
- if (entries.length === 0) return () => {};
56
- const aliasMap = new Map(entries);
57
- // Lazy-import Module to avoid loading it in browser/test contexts.
58
- let Module;
59
- try {
60
- const mod = await import('node:module');
61
- Module = mod.default ?? mod.Module ?? mod;
62
- } catch {
63
- return () => {};
64
- }
65
- if (!Module || typeof Module._resolveFilename !== 'function') return () => {};
66
- const original = Module._resolveFilename;
67
- const hooked = function dshloaderResolve(request, parent, ...rest) {
68
- const mapped = aliasMap.get(request);
69
- if (mapped !== undefined) return original.call(this, mapped, parent, ...rest);
70
- return original.call(this, request, parent, ...rest);
71
- };
72
- Module._resolveFilename = hooked;
73
- return () => {
74
- if (Module._resolveFilename === hooked) Module._resolveFilename = original;
75
- };
76
- }
77
-
78
- /**
79
- * @param {object} ctx cordis context
80
- * @param {{ exposeAllNamespaces?: boolean, hostPackageAliases?: Record<string,string> }} [config]
81
- */
82
- export function create(ctx, config = {}) {
83
- const exposeAllNamespaces = Boolean(config.exposeAllNamespaces);
84
- const packageAliases = { ...hostPackageAliases, ...(config.hostPackageAliases ?? {}) };
85
-
86
- async function apply() {
87
- // --- service alias: httpServer -> webServer (fix 2) ---
88
- if (ctx.get('httpServer') === undefined) {
89
- const webServer = ctx.get('webServer');
90
- if (webServer !== undefined) {
91
- ctx.reflect.provide('httpServer', webServer);
92
- console.log(`${LOG_PREFIX} aliased httpServer -> webServer`);
93
- }
94
- } else {
95
- console.log(`${LOG_PREFIX} httpServer already exists, skip alias`);
96
- }
97
-
98
- // --- host package-name aliases (CJS require interception) ---
99
- if (Object.keys(packageAliases).length > 0) {
100
- ctx.effect(
101
- () => installHostPackageAliases(packageAliases),
102
- 'dshloader: host package-name aliases',
103
- );
104
- console.log(`${LOG_PREFIX} installed host package aliases: ${Object.keys(packageAliases).join(', ')}`);
105
- }
106
-
107
- // --- settings bridge routes for the browser fetch path (fix 5, path 2) ---
108
- // Only registered when the profile explicitly opts in. The host-side
109
- // stable API (ctx.dshLoader.settings.*) is unaffected by this gate and
110
- // always available to host plugin code.
111
- if (exposeAllNamespaces) {
112
- ctx.effect(
113
- () => registerSettingsBridgeRoutes(ctx),
114
- 'dshloader: settings namespace bridge routes',
115
- );
116
- }
117
- }
118
-
119
- function dispose() {
120
- // No cordis-unaware resources held in v1; effect auto-recycle covers
121
- // everything registered in apply(). Implemented as a no-op to satisfy
122
- // the HostAdapter interface and future hot-swap scenarios.
123
- }
124
-
125
- return { supports, name, apply, dispose };
126
- }
127
-
128
- /**
129
- * Register host bridge routes that the client fetch interceptor forwards
130
- * non-whitelisted settings requests to. Mirrors dsh-upstream-fixes/lib/index.js
131
- * `registerRoutes` (settings section).
132
- */
133
- function registerSettingsBridgeRoutes(ctx) {
134
- const webServer = ctx.get('webServer') ?? ctx.get('httpServer');
135
- if (webServer === undefined || typeof webServer.register !== 'function') return () => {};
136
- return webServer.register({
137
- kind: 'prefix',
138
- path: BRIDGE_PREFIX,
139
- handler: (req, res) => {
140
- const json = (status, body) => {
141
- res.statusCode = status;
142
- res.setHeader('content-type', 'application/json');
143
- res.end(JSON.stringify(body));
144
- };
145
- const readBody = () =>
146
- new Promise((done, fail) => {
147
- let body = '';
148
- req.on?.('data', (chunk) => {
149
- body += chunk.toString('utf8');
150
- });
151
- req.on?.('end', () => done(body));
152
- req.on?.('error', fail);
153
- });
154
- const url = req.url ?? '/';
155
- const method = (req.method ?? 'GET').toUpperCase();
156
- const path = url.split('?')[0] ?? '/';
157
-
158
- void (async () => {
159
- try {
160
- // GET /api/dshloader/settings/describe — full redacted namespace list.
161
- if (method === 'GET' && (path === `${BRIDGE_PREFIX}/settings/describe` || path === `${BRIDGE_PREFIX}/settings/describe/`)) {
162
- const settings = ctx.get('settings');
163
- if (settings === undefined) {
164
- json(200, { ok: false, message: 'settings service unavailable' });
165
- return;
166
- }
167
- json(200, {
168
- ok: true,
169
- namespaces: settings.describe({ redactSecrets: true }).map(toNamespaceView),
170
- });
171
- return;
172
- }
173
-
174
- // POST /api/dshloader/settings/{update,mutate,replace} — write seam.
175
- const writeMatch = /^\/api\/dshloader\/settings\/(update|mutate|replace)$/.exec(path);
176
- if (method === 'POST' && writeMatch !== null) {
177
- const mode = writeMatch[1];
178
- let parsed = {};
179
- try {
180
- parsed = JSON.parse(await readBody());
181
- } catch {
182
- /* keep {} */
183
- }
184
- const ns = typeof parsed.ns === 'string' ? parsed.ns : '';
185
- if (ns.length === 0) {
186
- json(200, {
187
- ok: true,
188
- result: {
189
- ok: false,
190
- code: 'settings-rejected',
191
- message: 'settings write needs a namespace',
192
- details: {},
193
- },
194
- });
195
- return;
196
- }
197
- const settings = ctx.get('settings');
198
- const result = await runSettingsWrite(
199
- settings,
200
- mode,
201
- ns,
202
- mode === 'mutate' ? parsed.ops : parsed.section,
203
- parsed.expectedRevision,
204
- );
205
- json(200, { ok: true, result });
206
- return;
207
- }
208
-
209
- json(404, { ok: false, message: 'not found' });
210
- } catch (error) {
211
- json(500, { ok: false, message: error instanceof Error ? error.message : String(error) });
212
- }
213
- })();
214
- },
215
- });
216
- }
217
-
218
- async function runSettingsWrite(settings, mode, ns, section, expectedRevision) {
219
- if (settings === undefined) {
220
- return { ok: false, code: 'internal', message: 'settings service unavailable', details: {} };
221
- }
222
- try {
223
- if (mode === 'update') await settings.update(ns, section, expectedRevision);
224
- else if (mode === 'replace') await settings.replace(ns, section, expectedRevision);
225
- else await settings.mutate(ns, section, expectedRevision);
226
- } catch (error) {
227
- return settingsErrorToResult(error, ns, mode);
228
- }
229
- const descriptor = settings
230
- .describe({ redactSecrets: true })
231
- .find((d) => String(d.ns) === String(ns));
232
- if (descriptor === undefined) {
233
- return { ok: false, code: 'internal', message: 'settings namespace disposed after write', details: { ns } };
234
- }
235
- return { ok: true, value: toNamespaceView(descriptor) };
236
- }