From 69a4820369c6c1b00f5f4622275064e127e73e9f Mon Sep 17 00:00:00 2001 From: oliver Date: Fri, 4 Sep 2026 22:38:52 +0800 Subject: [PATCH] Stop requiring dsh-client-store in the Plugins card bundle. Shipped dsh web seed has no client-store entry; use a private snapshot store and only externalize platform seed modules. Co-authored-by: Cursor --- lib/client.js | 23 +++++++++++++++++++-- package.json | 3 +-- scripts/build-client.mjs | 39 ++++++++++++++++++------------------ src/client/card-form.ts | 2 +- src/client/controller.ts | 2 +- src/client/snapshot-store.ts | 34 +++++++++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 src/client/snapshot-store.ts diff --git a/lib/client.js b/lib/client.js index 9a33444..1ed115c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -333,8 +333,27 @@ function NetxopsCard(props) { }); } +// src/client/snapshot-store.ts +function createSnapshotStore(init) { + let state = init; + const listeners = new Set; + return { + getSnapshot: () => state, + subscribe: (listener) => { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; + }, + set: (next) => { + state = next; + for (const listener of [...listeners]) + listener(); + } + }; +} + // src/client/card-form.ts -var import_dsh_client_store = require("@deepseek-ai/dsh-client-store"); function textField(field) { return { field, @@ -363,7 +382,7 @@ class CardForm { }); } bind(project) { - const store = import_dsh_client_store.createSnapshotStore(project()); + const store = createSnapshotStore(project()); this.listeners.add(() => { store.set(project()); }); diff --git a/package.json b/package.json index e2e002c..fccb823 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dsh-netxops", - "version": "0.1.6", + "version": "0.1.7", "description": "DeepSeek Harness Netx Ops: host bridge + Plugins settings card + agent preset", "license": "MIT", "type": "module", @@ -70,7 +70,6 @@ "@deepseek-ai/dsh-tool-fs": "*", "@deepseek-ai/dsh-tool-jobs": "*", "@deepseek-ai/dsh-agent-instructions": "*", - "@deepseek-ai/dsh-client-store": "*", "@deepseek-ai/dsh-client-locale": "*", "@deepseek-ai/dsh-client-ui-settings": "*", "@deepseek-ai/dsh-client-ui-settings-plugins": "*", diff --git a/scripts/build-client.mjs b/scripts/build-client.mjs index bfdb92b..a637d90 100644 --- a/scripts/build-client.mjs +++ b/scripts/build-client.mjs @@ -2,8 +2,12 @@ * Build lib/client.js as a DSH ModuleLoader factory artifact using Bun's bundler. * Usage: bun run scripts/build-client.mjs * - * Must emit production JSX (`react/jsx-runtime`). The web shell seeds only that - * specifier — `react/jsx-dev-runtime` is not in PLATFORM_MODULES and fails load. + * Externals must match the *installed* web shell seed table. Shipped + * `@deepseek-ai/dsh` 0.1.1-rc.2 seeds only: + * react, react/jsx-runtime, react-dom, react-dom/client, + * @deepseek-ai/cordis, dsh-client-ui-slots, dsh-client-ui-primitives + * Do not leave other @deepseek-ai packages as require() targets unless they + * are also listed in dsh.client.inject and arrive as graph factories. */ import { mkdirSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' @@ -14,28 +18,15 @@ const entry = join(root, 'src/client/index.ts') const outFile = join(root, 'lib', 'client.js') const id = 'dsh-netxops' -/** Align with @deepseek-ai/dsh-client-web PLATFORM_MODULES + dsh.client.inject. */ +/** Exact PLATFORM_MODULES keys from shipped dsh-web-frontend seed. */ const external = [ 'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', '@deepseek-ai/cordis', - '@deepseek-ai/dsh-client-store', - '@deepseek-ai/dsh-client-locale', - '@deepseek-ai/dsh-client-locale/client', - '@deepseek-ai/dsh-client-ui-settings', - '@deepseek-ai/dsh-client-ui-settings/client', - '@deepseek-ai/dsh-client-ui-settings-plugins', - '@deepseek-ai/dsh-client-ui-settings-plugins/client', '@deepseek-ai/dsh-client-ui-slots', - '@deepseek-ai/dsh-client-ui-renderer', - '@deepseek-ai/dsh-client-ui-renderer/client', '@deepseek-ai/dsh-client-ui-primitives', - '@deepseek-ai/dsh-api-remotes', - '@deepseek-ai/dsh-api-remotes/client', - '@deepseek-ai/dsh-client-connection', - '@deepseek-ai/dsh-client-connection/client', ] const result = await Bun.build({ @@ -44,7 +35,6 @@ const result = await Bun.build({ format: 'cjs', sourcemap: 'none', minify: false, - // Force production JSX transform (jsx-runtime, not jsx-dev-runtime). define: { 'process.env.NODE_ENV': JSON.stringify('production'), }, @@ -56,10 +46,21 @@ if (!result.success) { throw new Error('Bun.build failed') } -let raw = await result.outputs[0].text() +const raw = await result.outputs[0].text() if (raw.includes('react/jsx-dev-runtime')) { throw new Error('client bundle still references react/jsx-dev-runtime; refuse to ship') } +if (raw.includes('@deepseek-ai/dsh-client-store')) { + throw new Error('client bundle still requires dsh-client-store; refuse to ship') +} + +const requires = [...raw.matchAll(/require\("([^"]+)"\)/g)].map(m => m[1]) +const allowed = new Set(external) +for (const spec of requires) { + if (!allowed.has(spec)) { + throw new Error(`client bundle requires undeclared external "${spec}"; refuse to ship`) + } +} const body = raw .replace(/^"use strict";\s*/m, '') @@ -79,4 +80,4 @@ ${body} mkdirSync(dirname(outFile), { recursive: true }) writeFileSync(outFile, artifact, 'utf8') -console.log(`wrote ${outFile} (${artifact.length} bytes)`) +console.log(`wrote ${outFile} (${artifact.length} bytes); requires: ${[...new Set(requires)].join(', ') || '(none)'}`) diff --git a/src/client/card-form.ts b/src/client/card-form.ts index 980a385..6aa39cd 100644 --- a/src/client/card-form.ts +++ b/src/client/card-form.ts @@ -3,8 +3,8 @@ * Self-contained (cannot value-import ui-settings-plugins — client bundle purity). */ -import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store' import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client' +import { createSnapshotStore, type SnapshotStore } from './snapshot-store.ts' export type FieldWrite = { kind: 'set'; value: unknown } | { kind: 'clear' } diff --git a/src/client/controller.ts b/src/client/controller.ts index 3863b97..70890bc 100644 --- a/src/client/controller.ts +++ b/src/client/controller.ts @@ -4,8 +4,8 @@ import type { Context as ClientContext } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-api-remotes/client' -import type { SnapshotStore } from '@deepseek-ai/dsh-client-store' import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client' +import type { SnapshotStore } from './snapshot-store.ts' import { CardForm, textField, type CardActions, type CardFieldState, type CardShell, diff --git a/src/client/snapshot-store.ts b/src/client/snapshot-store.ts new file mode 100644 index 0000000..c5863da --- /dev/null +++ b/src/client/snapshot-store.ts @@ -0,0 +1,34 @@ +/** + * Minimal snapshot store for the Plugins card. + * + * Shipped DSH web (`@deepseek-ai/dsh` ≤0.1.1-rc.2) seeds only react / cordis / + * ui-slots / ui-primitives — not `@deepseek-ai/dsh-client-store`. Keep this + * private so the client bundle does not require that package. + */ + +export interface SnapshotStore { + getSnapshot: () => T + subscribe: (listener: () => void) => () => void + set: (next: T) => void +} + +/** + * Create a synchronous snapshot store (same-tick notify for controlled inputs). + * @param init - initial snapshot. + * @returns the store. + */ +export function createSnapshotStore(init: T): SnapshotStore { + let state = init + const listeners = new Set<() => void>() + return { + getSnapshot: () => state, + subscribe: (listener) => { + listeners.add(listener) + return () => { listeners.delete(listener) } + }, + set: (next) => { + state = next + for (const listener of [...listeners]) listener() + }, + } +}