dsh-logicprobe 0.6.8 → 0.7.1

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.
package/src/client.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * logicprobe — browser half: the gate-injection switch on the dsh Web client's
3
+ * Plugins page.
4
+ *
5
+ * The Plugins page (`@deepseek-ai/dsh-client-ui-plugin-manager`) owns the
6
+ * sidebar **Plugins** entry and declares the slots a bundle's own configuration
7
+ * registers into. This module contributes one `plugins.bundle.config` entry,
8
+ * keyed by this package's npm name, so the switch renders on logicprobe's own
9
+ * page between its description and its rows.
10
+ *
11
+ * Why the switch writes through `configForms` rather than reaching for the
12
+ * profile file: dsh's settings service exposes only the Config fields declared
13
+ * `.volatile()`, and it rejects a write to any other path. `enabled` is such a
14
+ * field (see `src/index.ts`), so flipping the switch is an ordinary
15
+ * revision-fenced settings write that the running Host picks up in place — the
16
+ * injection there re-reads the reference on every model step.
17
+ *
18
+ * Shape: this is a prebuilt module-system bundle, not a source module. It calls
19
+ * `window.__ModuleLoader__.load({ id, factory })` with this package's resolved
20
+ * npm name, and `factory` returns the cordis plugin face. Only the client
21
+ * baseline is requested (`react` and
22
+ * `@deepseek-ai/dsh-client-ui-primitives`); every other capability arrives
23
+ * through cordis `inject`. `scripts/build-client.mjs` publishes this file
24
+ * verbatim as `lib/client.js`.
25
+ *
26
+ * @module dsh-logicprobe/client
27
+ */
28
+
29
+ window.__ModuleLoader__.load({
30
+ id: 'dsh-logicprobe',
31
+ factory: (require) => {
32
+ const React = require('react')
33
+ const { Button, Switch } = require('@deepseek-ai/dsh-client-ui-primitives')
34
+
35
+ /** Settings namespace: the Loader entry id this bundle's patch declares. */
36
+ const NS = 'logicprobe'
37
+ /** `plugins.bundle.config` key: the bundle's npm package name. */
38
+ const PACKAGE = 'dsh-logicprobe'
39
+ /** This page's dictionary namespace. */
40
+ const LOCALE_NS = 'logicprobe.plugins'
41
+ /** The Config field the switch writes inside the namespace's section. */
42
+ const FIELD = 'enabled'
43
+
44
+ /** English copy. */
45
+ const en = {
46
+ title: 'Gate injection',
47
+ label: 'Inject the gate text',
48
+ hint: 'Folds the claim-verification doctrine into the first model step of every session. Turning it off leaves the skills and the verification tools registered — only the injected text is dropped.',
49
+ overridden: 'Overridden',
50
+ reset: 'Reset to default',
51
+ readOnly: 'This deployment stores settings read-only.',
52
+ unavailable: 'This plugin is not loaded, so it cannot be configured right now.',
53
+ saveFailed: 'The deployment did not accept that value; the switch shows what is stored.',
54
+ }
55
+ /** Simplified Chinese copy. */
56
+ const zh = {
57
+ title: 'Gate 注入',
58
+ label: '注入 gate 文本',
59
+ hint: '把 claim 核查铁律折进每个会话的第一个模型步。关掉后 skills 与验证工具仍然注册,只是不再注入那段提示文本。',
60
+ overridden: '已覆盖',
61
+ reset: '恢复默认',
62
+ readOnly: '本部署的设置为只读。',
63
+ unavailable: '该插件当前未加载,暂时无法配置。',
64
+ saveFailed: '本部署没有接受这个值,开关显示的是已存下的状态。',
65
+ }
66
+
67
+ /** Required cordis services. */
68
+ const inject = ['slots', 'locale', 'configForms']
69
+
70
+ const GROUP = { display: 'flex', flexDirection: 'column', gap: '8px' }
71
+ const TITLE = { margin: 0, fontSize: '14px', fontWeight: '500', lineHeight: '22px' }
72
+ const ROW = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '16px' }
73
+ const LABEL = { fontSize: '13px', lineHeight: '20px' }
74
+ const NOTE = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }
75
+ const FAILED = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }
76
+
77
+ /**
78
+ * Whether a settings-layer value carries this field, which is what marks it
79
+ * overridden: an override equal to the default is still an override.
80
+ * @param layer - the raw user layer the form snapshot carries.
81
+ * @returns whether the layer holds the field.
82
+ */
83
+ function carries(layer) {
84
+ return layer !== null && typeof layer === 'object' && Object.prototype.hasOwnProperty.call(layer, FIELD)
85
+ }
86
+
87
+ /**
88
+ * Render the gate-injection switch, or the note saying why it cannot render.
89
+ * @param props - the page's `t` seat, the bound form snapshot hook, and the write actions.
90
+ * @returns the body of this bundle's configuration section.
91
+ */
92
+ function InjectionCard(props) {
93
+ const t = props.t
94
+ const state = props.useInjectionForm((snapshot) => snapshot)
95
+ const [pending, setPending] = React.useState(false)
96
+ const [failed, setFailed] = React.useState(false)
97
+
98
+ /** Run one settings write and report a refusal or a transport failure. */
99
+ const write = (run) => {
100
+ setPending(true)
101
+ setFailed(false)
102
+ Promise.resolve(run()).then(
103
+ (accepted) => {
104
+ setPending(false)
105
+ setFailed(accepted === false)
106
+ },
107
+ () => {
108
+ setPending(false)
109
+ setFailed(true)
110
+ },
111
+ )
112
+ }
113
+
114
+ if (state.status !== 'ready') {
115
+ return React.createElement('p', { style: NOTE }, t('unavailable'))
116
+ }
117
+
118
+ const section = state.value !== null && typeof state.value === 'object' ? state.value : {}
119
+ // The schema default is `true`; only an explicit false means off.
120
+ const checked = section[FIELD] !== false
121
+ const overridden = carries(state.user)
122
+ const locked = state.writable !== true || pending
123
+
124
+ const children = [
125
+ React.createElement('h4', { key: 'title', style: TITLE }, t('title')),
126
+ React.createElement('div', { key: 'row', style: ROW }, [
127
+ React.createElement('span', { key: 'label', style: LABEL }, t('label')),
128
+ React.createElement(Switch, {
129
+ key: 'switch',
130
+ checked,
131
+ disabled: locked,
132
+ label: t('label'),
133
+ onChange: (next) => write(() => props.setEnabled(next)),
134
+ }),
135
+ ]),
136
+ React.createElement('p', { key: 'hint', style: NOTE }, state.writable === true ? t('hint') : t('readOnly')),
137
+ ]
138
+
139
+ if (overridden) {
140
+ children.push(
141
+ React.createElement('div', { key: 'overridden', style: ROW }, [
142
+ React.createElement('span', { key: 'badge', style: NOTE }, t('overridden')),
143
+ React.createElement(
144
+ Button,
145
+ {
146
+ key: 'reset',
147
+ variant: 'outline',
148
+ size: 'sm',
149
+ disabled: locked,
150
+ onClick: () => write(() => props.resetEnabled()),
151
+ },
152
+ t('reset'),
153
+ ),
154
+ ]),
155
+ )
156
+ }
157
+
158
+ if (failed) {
159
+ children.push(React.createElement('p', { key: 'failed', style: FAILED, role: 'alert' }, t('saveFailed')))
160
+ }
161
+
162
+ return React.createElement('div', { style: GROUP }, children)
163
+ }
164
+
165
+ /**
166
+ * Mount the switch while the Host serves logicprobe's settings namespace.
167
+ * @param ctx - the browser plugin context.
168
+ */
169
+ function apply(ctx) {
170
+ ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'dsh-logicprobe: dictionaries')
171
+ // `whileServed` is the registration barrier that keeps this page alive only
172
+ // while the Host serves the namespace. Hosts predating it (measured: dsh
173
+ // 0.1.5-rc.3 and 0.1.6-alpha.2) have no live settings field to offer at all,
174
+ // so there is nothing to register — and calling it there would throw during
175
+ // this plugin's own activation, which the Web boot audit then reports as a
176
+ // failed client entry. Degrade to no page instead.
177
+ if (typeof ctx.configForms.whileServed !== 'function') return
178
+ // The page renders the section only for a bundle whose package name is in
179
+ // its configuration ledger, and the ledger follows this registration. The
180
+ // registration in turn waits for the namespace to be served, so a profile
181
+ // whose logicprobe row is switched off shows no trace of the switch.
182
+ ctx.effect(
183
+ () =>
184
+ ctx.configForms.whileServed([NS], () => {
185
+ const form = ctx.configForms.get(NS)
186
+ const source = {
187
+ getSnapshot: () => form.getSnapshot(),
188
+ subscribe: (listener) => form.subscribe(listener),
189
+ }
190
+ return ctx.slots.inject('plugins.bundle.config', () =>
191
+ ctx.slots.register(
192
+ {
193
+ name: 'plugins.bundle.config',
194
+ key: PACKAGE,
195
+ locale: LOCALE_NS,
196
+ inject: () => ({
197
+ hooks: { injectionForm: source },
198
+ setEnabled: (next) => form.set(FIELD, next),
199
+ resetEnabled: () => form.unset(FIELD),
200
+ }),
201
+ },
202
+ InjectionCard,
203
+ ),
204
+ )
205
+ }),
206
+ 'dsh-logicprobe: gate-injection switch',
207
+ )
208
+ }
209
+
210
+ return { inject, apply }
211
+ },
212
+ })