dsh-embedded-workbench 0.9.0 → 0.9.2

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/lib/client.js ADDED
@@ -0,0 +1,269 @@
1
+ /**
2
+ * embedded-workbench — browser half: the gate-injection switch on the dsh Web
3
+ * client's 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
9
+ * embedded-workbench's own 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-embedded-workbench/client
27
+ */
28
+
29
+ window.__ModuleLoader__.load({
30
+ id: 'dsh-embedded-workbench',
31
+ factory: (require) => {
32
+ const React = require('react')
33
+ // The host's Switch and Button, vendored. A plain-JS client bundle must not
34
+ // require a Harness Client package: it changes without notice, we get no type
35
+ // check, and a throw blanks this slot entry. Markup, classes, token usage and
36
+ // the aria behaviour follow the host primitive; classes carry our own prefix.
37
+ const CONTROLS_CSS = [
38
+ '.ew-switch { box-sizing: border-box; position: relative; flex: 0 0 auto; width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 999px; corner-shape: round; background: var(--dsw-alias-border-l3); cursor: pointer; }',
39
+ ".ew-switch[aria-checked='true'] { background: var(--dsw-alias-brand-primary); }",
40
+ '.ew-switch:disabled { cursor: default; opacity: 0.5; }',
41
+ '.ew-switch:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }',
42
+ '.ew-switch-thumb { display: block; width: 16px; height: 16px; border-radius: 50%; corner-shape: round; background: var(--dsw-alias-label-primary-foreground); transition: transform 120ms ease; }',
43
+ ".ew-switch[aria-checked='false'] .ew-switch-thumb { background: var(--dsw-alias-switch-thumb); }",
44
+ ".ew-switch[aria-checked='true'] .ew-switch-thumb { transform: translateX(16px); }",
45
+ '.ew-btn { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 4px; border: none; border-radius: var(--dsw-radius-md); cursor: pointer; font-size: 14px; line-height: 22px; color: var(--dsw-alias-label-primary); background: transparent; padding: 0 14px; }',
46
+ '.ew-btn:disabled { cursor: not-allowed; opacity: 0.4; }',
47
+ '.ew-btn-sm { height: 28px; font-size: 12px; line-height: 18px; padding: 0 10px; border-radius: var(--dsw-radius-sm); }',
48
+ '.ew-btn-outline { border: 0.5px solid var(--dsw-alias-border-l3); background: transparent; }',
49
+ '.ew-btn-outline:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }',
50
+ ].join('\n')
51
+
52
+ /** Rendered inside the component tree, so unmounting removes the styles. */
53
+ function Controls() {
54
+ return React.createElement('style', null, CONTROLS_CSS)
55
+ }
56
+
57
+ function Switch(props) {
58
+ return React.createElement(
59
+ 'button',
60
+ {
61
+ type: 'button',
62
+ role: 'switch',
63
+ 'aria-checked': props.checked === true,
64
+ 'aria-label': props.label,
65
+ className: 'ew-switch',
66
+ disabled: props.disabled === true,
67
+ onClick: () => {
68
+ if (props.disabled !== true) props.onChange(!(props.checked === true))
69
+ },
70
+ },
71
+ React.createElement('span', { className: 'ew-switch-thumb' }),
72
+ )
73
+ }
74
+
75
+ Switch.displayName = 'dsh-embedded-workbench:Switch'
76
+ Button.displayName = 'dsh-embedded-workbench:Button'
77
+
78
+ function Button(props) {
79
+ const classes = ['ew-btn']
80
+ if (props.size === 'sm') classes.push('ew-btn-sm')
81
+ if (props.variant === 'outline') classes.push('ew-btn-outline')
82
+ return React.createElement(
83
+ 'button',
84
+ { type: 'button', className: classes.join(' '), disabled: props.disabled === true, onClick: props.onClick },
85
+ props.children,
86
+ )
87
+ }
88
+
89
+ /** Settings namespace: the Loader entry id this bundle's patch declares. */
90
+ const NS = 'embedded-workbench'
91
+ /** `plugins.bundle.config` key: the bundle's npm package name. */
92
+ const PACKAGE = 'dsh-embedded-workbench'
93
+ /** This page's dictionary namespace. */
94
+ const LOCALE_NS = 'embedded-workbench.plugins'
95
+ /** The Config field the switch writes inside the namespace's section. */
96
+ const FIELD = 'enabled'
97
+
98
+ /** English copy. */
99
+ const en = {
100
+ title: 'Gate injection',
101
+ label: 'Inject the gate text',
102
+ hint: 'Folds the Plan Verification Gate and the context-budget rule into the first model step of every session. Turning it off leaves all eight skills registered — only the injected text is dropped.',
103
+ overridden: 'Overridden',
104
+ reset: 'Reset to default',
105
+ readOnly: 'This deployment stores settings read-only.',
106
+ unavailable: 'This plugin is not loaded, so it cannot be configured right now.',
107
+ saveFailed: 'The deployment did not accept that value; the switch shows what is stored.',
108
+ }
109
+ /** Simplified Chinese copy. */
110
+ const zh = {
111
+ title: 'Gate 注入',
112
+ label: '注入 gate 文本',
113
+ hint: '把 Plan Verification Gate 与上下文预算规则折进每个会话的第一个模型步。关掉后 8 个技能仍然注册,只是不再注入那段提示文本。',
114
+ overridden: '已覆盖',
115
+ reset: '恢复默认',
116
+ readOnly: '本部署的设置为只读。',
117
+ unavailable: '该插件当前未加载,暂时无法配置。',
118
+ saveFailed: '本部署没有接受这个值,开关显示的是已存下的状态。',
119
+ }
120
+
121
+ /** Required cordis services. */
122
+ const inject = ['slots', 'locale', 'configForms']
123
+
124
+ const GROUP = { display: 'flex', flexDirection: 'column', gap: '8px' }
125
+ const TITLE = { margin: 0, fontSize: '14px', fontWeight: '500', lineHeight: '22px' }
126
+ const ROW = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '16px' }
127
+ const LABEL = { fontSize: '13px', lineHeight: '20px' }
128
+ const NOTE = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }
129
+ const FAILED = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }
130
+
131
+ /**
132
+ * Whether a settings-layer value carries this field, which is what marks it
133
+ * overridden: an override equal to the default is still an override.
134
+ * @param layer - the raw user layer the form snapshot carries.
135
+ * @returns whether the layer holds the field.
136
+ */
137
+ function carries(layer) {
138
+ return layer !== null && typeof layer === 'object' && Object.prototype.hasOwnProperty.call(layer, FIELD)
139
+ }
140
+
141
+ /**
142
+ * Render the gate-injection switch, or the note saying why it cannot render.
143
+ * @param props - the page's `t` seat, the bound form snapshot hook, and the write actions.
144
+ * @returns the body of this bundle's configuration section.
145
+ */
146
+ function InjectionCard(props) {
147
+ const t = props.t
148
+ const state = props.useInjectionForm((snapshot) => snapshot)
149
+ const [pending, setPending] = React.useState(false)
150
+ const [failed, setFailed] = React.useState(false)
151
+
152
+ /** Run one settings write and report a refusal or a transport failure. */
153
+ const write = (run) => {
154
+ setPending(true)
155
+ setFailed(false)
156
+ Promise.resolve(run()).then(
157
+ (accepted) => {
158
+ setPending(false)
159
+ setFailed(accepted === false)
160
+ },
161
+ () => {
162
+ setPending(false)
163
+ setFailed(true)
164
+ },
165
+ )
166
+ }
167
+
168
+ if (state.status !== 'ready') {
169
+ return React.createElement('p', { style: NOTE }, t('unavailable'))
170
+ }
171
+
172
+ const section = state.value !== null && typeof state.value === 'object' ? state.value : {}
173
+ // The schema default is `true`; only an explicit false means off.
174
+ const checked = section[FIELD] !== false
175
+ const overridden = carries(state.user)
176
+ const locked = state.writable !== true || pending
177
+
178
+ const children = [
179
+ React.createElement(Controls, { key: 'styles' }),
180
+ React.createElement('h4', { key: 'title', style: TITLE }, t('title')),
181
+ React.createElement('div', { key: 'row', style: ROW }, [
182
+ React.createElement('span', { key: 'label', style: LABEL }, t('label')),
183
+ React.createElement(Switch, {
184
+ key: 'switch',
185
+ checked,
186
+ disabled: locked,
187
+ label: t('label'),
188
+ onChange: (next) => write(() => props.setEnabled(next)),
189
+ }),
190
+ ]),
191
+ React.createElement('p', { key: 'hint', style: NOTE }, state.writable === true ? t('hint') : t('readOnly')),
192
+ ]
193
+
194
+ if (overridden) {
195
+ children.push(
196
+ React.createElement('div', { key: 'overridden', style: ROW }, [
197
+ React.createElement('span', { key: 'badge', style: NOTE }, t('overridden')),
198
+ React.createElement(
199
+ Button,
200
+ {
201
+ key: 'reset',
202
+ variant: 'outline',
203
+ size: 'sm',
204
+ disabled: locked,
205
+ onClick: () => write(() => props.resetEnabled()),
206
+ },
207
+ t('reset'),
208
+ ),
209
+ ]),
210
+ )
211
+ }
212
+
213
+ if (failed) {
214
+ children.push(React.createElement('p', { key: 'failed', style: FAILED, role: 'alert' }, t('saveFailed')))
215
+ }
216
+
217
+ return React.createElement('div', { style: GROUP }, children)
218
+ }
219
+
220
+ /**
221
+ * Mount the switch while the Host serves embedded-workbench's settings
222
+ * namespace.
223
+ * @param ctx - the browser plugin context.
224
+ */
225
+ function apply(ctx) {
226
+ ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'dsh-embedded-workbench: dictionaries')
227
+ // `whileServed` is the registration barrier that keeps this page alive only
228
+ // while the Host serves the namespace. Hosts predating it (measured: dsh
229
+ // 0.1.5-rc.3 and 0.1.6-alpha.2) have no live settings field to offer at all,
230
+ // so there is nothing to register — and calling it there would throw during
231
+ // this plugin's own activation, which the Web boot audit then reports as a
232
+ // failed client entry. Degrade to no page instead.
233
+ if (typeof ctx.configForms.whileServed !== 'function') return
234
+ // The page renders the section only for a bundle whose package name is in
235
+ // its configuration ledger, and the ledger follows this registration. The
236
+ // registration in turn waits for the namespace to be served, so a profile
237
+ // whose embedded-workbench row is switched off shows no trace of the
238
+ // switch.
239
+ ctx.effect(
240
+ () =>
241
+ ctx.configForms.whileServed([NS], () => {
242
+ const form = ctx.configForms.get(NS)
243
+ const source = {
244
+ getSnapshot: () => form.getSnapshot(),
245
+ subscribe: (listener) => form.subscribe(listener),
246
+ }
247
+ return ctx.slots.inject('plugins.bundle.config', () =>
248
+ ctx.slots.register(
249
+ {
250
+ name: 'plugins.bundle.config',
251
+ key: PACKAGE,
252
+ locale: LOCALE_NS,
253
+ inject: () => ({
254
+ hooks: { injectionForm: source },
255
+ setEnabled: (next) => form.set(FIELD, next),
256
+ resetEnabled: () => form.unset(FIELD),
257
+ }),
258
+ },
259
+ InjectionCard,
260
+ ),
261
+ )
262
+ }),
263
+ 'dsh-embedded-workbench: gate-injection switch',
264
+ )
265
+ }
266
+
267
+ return { inject, apply }
268
+ },
269
+ })
package/lib/index.js CHANGED
@@ -51,13 +51,49 @@ Plugin embedded-workbench is active: embedded C/C++ firmware development skills
51
51
 
52
52
  To load the workflows and engineering policies behind these skills: load the embedded-workbench skill.
53
53
  </EXTREMELY_IMPORTANT>`;
54
+ /**
55
+ * Declare a field as live where this host's schemastery can — `.volatile()`
56
+ * arrived in 3.18.3 — and leave it an ordinary field where it cannot.
57
+ *
58
+ * The fallback is load-bearing, not defensive padding. `Config` below is built
59
+ * while this module is still being evaluated, so an unconditional `.volatile()`
60
+ * on a host shipping schemastery 3.18.2 (measured: dsh 0.1.5-rc.2 and
61
+ * 0.1.5-rc.3) throws during import; the loader entry then fails and takes the
62
+ * WHOLE plugin tree — and the host's boot — down with it. Degrading costs only
63
+ * the Plugins-page switch, because the settings service projects nothing but
64
+ * fields under a `.volatile()` node; the skills and the gate injection are
65
+ * untouched. The returned schema keeps the plain field's static type; the
66
+ * `Config` interface below carries the union the host actually hands over.
67
+ */
68
+ function live(field) {
69
+ const probe = field;
70
+ return typeof probe.volatile === 'function' ? probe.volatile() : field;
71
+ }
54
72
  export const Config = z.object({
73
+ // Live so the Web Plugins page can flip the gate injection inside a running
74
+ // session: dsh's settings service projects ONLY fields under a `.volatile()`
75
+ // node and rejects writes to every other path. The price is that the injection
76
+ // reads the reference per step instead of deciding once at mount, which is also
77
+ // what lets a toggle take effect without remounting the row.
78
+ //
55
79
  // On by default, but deliberately small: the payload carries the verification
56
80
  // gate and the context-budget rule only, so leaving it on costs a few hundred
57
81
  // tokens once per session rather than the ~900 of the previous payload.
58
- enabled: z.boolean().default(true),
82
+ enabled: live(z.boolean().default(true)),
83
+ // Not volatile, deliberately: the settings projection feeds a GUI form, and a
84
+ // multi-kilobyte text field does not belong in one. Override it in the
85
+ // profile's `cordis.patch.yml` row instead.
59
86
  gateContent: z.string().default(DEFAULT_GATE_CONTENT),
60
87
  });
88
+ /**
89
+ * Read the injection switch as a boolean, whichever shape this host produced.
90
+ * @param config - the resolved plugin configuration.
91
+ * @returns whether the gate may be injected.
92
+ */
93
+ function injectionEnabled(config) {
94
+ const value = config.enabled;
95
+ return typeof value === 'boolean' ? value : value.get();
96
+ }
61
97
  function gateMessage(text) {
62
98
  return createUserMessage({
63
99
  content: [{ type: 'text', text }],
@@ -111,7 +147,7 @@ function inspectProvider(config) {
111
147
  query: async (method) => {
112
148
  if (method === 'status') {
113
149
  return {
114
- enabled: config.enabled,
150
+ enabled: injectionEnabled(config),
115
151
  gateContentLength: config.gateContent.length,
116
152
  };
117
153
  }
@@ -155,8 +191,12 @@ export function apply(ctx, config) {
155
191
  customSkillDirs: [SKILLS_DIR],
156
192
  });
157
193
  });
158
- if (!config.enabled)
159
- return;
194
+ // Injection listens unconditionally, including while the switch is off: the
195
+ // switch is volatile, so `apply` runs once and the value behind it can turn on
196
+ // later from the Web Plugins page. Returning early on a false value here would
197
+ // freeze that decision for the lifetime of the mount, and turning the switch
198
+ // back on could never take effect without a profile restart.
199
+ //
160
200
  // Inject the gate once per session on the FIRST model step that runs,
161
201
  // instead of at session-start: session-start injection lands in the agent's
162
202
  // inbox, which a blank-session preset switch (agentPreset.select ->
@@ -173,6 +213,8 @@ export function apply(ctx, config) {
173
213
  if (decision.kind === 'reject')
174
214
  return decision;
175
215
  registerProvider();
216
+ if (!injectionEnabled(config))
217
+ return decision;
176
218
  if (gateInHistory(agent.session))
177
219
  return decision;
178
220
  return {
@@ -27,7 +27,7 @@
27
27
  *
28
28
  * @module embedded-workbench-dsh
29
29
  */
30
- import type { Context } from '@deepseek-ai/cordis';
30
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
31
31
  import z from '@deepseek-ai/schemastery';
32
32
  import type { ContextFormed } from '@deepseek-ai/dsh-llm';
33
33
  declare module '@deepseek-ai/dsh-llm' {
@@ -40,7 +40,13 @@ declare module '@deepseek-ai/dsh-llm' {
40
40
  export declare const name = "embedded-workbench";
41
41
  export declare const inject: string[];
42
42
  export interface Config {
43
- enabled: boolean;
43
+ /**
44
+ * The injection switch the Web client's Plugins page edits live: a `Volatile`
45
+ * reference on a host whose schemastery supports one, an ordinary boolean on a
46
+ * host that predates `.volatile()`. Read it through {@link injectionEnabled},
47
+ * which accepts both shapes.
48
+ */
49
+ enabled: Volatile<boolean> | boolean;
44
50
  gateContent: string;
45
51
  }
46
52
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Embedded Workbench",
4
+ "description": "Embedded C/C++ firmware workflow: build, debug, fault triage, and plan verification skills."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "嵌入式工作台",
4
+ "description": "嵌入式 C/C++ 固件工作流:构建、调试、故障定位与计划核验技能。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-embedded-workbench",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Embedded C/C++ firmware development toolbox — 4 agents, 8 skills covering FreeRTOS, ISR, NVM storage, Keil MDK, ARMCLANG, HardFault, state machines, architecture, LVGL patterns, and claim fact-checking. Ships a native DeepSeek Harness (dsh) bundle that folds a short gate — the Plan Verification Gate plus a context-budget rule — into the first model step.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -11,13 +11,16 @@
11
11
  "default": "./lib/index.js"
12
12
  },
13
13
  "./cordis.patch.yml": "./cordis.patch.yml",
14
- "./package.json": "./package.json"
14
+ "./client": "./lib/client.js",
15
+ "./package.json": "./package.json",
16
+ "./locale/*.json": "./locale/*.json"
15
17
  },
16
18
  "files": [
17
19
  "lib",
18
20
  "src",
19
21
  "skills",
20
- "cordis.patch.yml"
22
+ "cordis.patch.yml",
23
+ "locale"
21
24
  ],
22
25
  "engines": {
23
26
  "node": ">=20"
@@ -31,8 +34,16 @@
31
34
  "bundle": {
32
35
  "patch": "./cordis.patch.yml"
33
36
  },
37
+ "client": {
38
+ "platform": "web",
39
+ "inject": [
40
+ "@deepseek-ai/dsh-client-locale",
41
+ "@deepseek-ai/dsh-client-ui-settings",
42
+ "@deepseek-ai/dsh-client-ui-plugin-manager"
43
+ ]
44
+ },
34
45
  "compatibility": {
35
- "dsh": "^0.1.0-rc.7 || ^0.1.1-rc.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ^0.1.5-alpha.2 || ^0.1.5-rc.2 || ^0.1.5-rc.3 || ^0.1.6-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1 || ^0.1.7-alpha.2 || ^0.1.7-rc.1 || ^0.1.7-rc.2",
46
+ "dsh": "^0.1.0-rc.7 || ^0.1.1-rc.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ^0.1.5-alpha.2 || ^0.1.5-rc.2 || ^0.1.5-rc.3 || ^0.1.6-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1 || ^0.1.7-alpha.2 || ^0.1.7-rc.1 || ^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
36
47
  "dshReleases": {
37
48
  "0.1.0-rc.7": "compatible",
38
49
  "0.1.0-rc.8": "compatible",
@@ -55,39 +66,43 @@
55
66
  "0.1.7-alpha.1": "compatible",
56
67
  "0.1.7-alpha.2": "compatible",
57
68
  "0.1.7-rc.1": "compatible",
58
- "0.1.7-rc.2": "compatible"
69
+ "0.1.7-rc.2": "compatible",
70
+ "0.2.0-rc.1": "compatible",
71
+ "0.2.0-rc.2": "compatible",
72
+ "0.2.1-alpha.1": "compatible",
73
+ "0.2.1-alpha.2": "compatible"
59
74
  },
60
75
  "profiles": [
61
- "headless"
76
+ "headless",
77
+ "web"
62
78
  ]
63
79
  }
64
80
  },
65
81
  "scripts": {
66
- "build": "tsc -p tsconfig.json",
82
+ "build": "tsc -p tsconfig.json && node scripts/build-client.mjs",
67
83
  "typecheck": "tsc -p tsconfig.json --noEmit",
68
84
  "bump": "node scripts/bump-version.mjs"
69
85
  },
70
86
  "peerDependencies": {
71
87
  "@deepseek-ai/cordis": "^4.0.4",
72
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
73
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
74
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
75
- "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8",
88
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
89
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
90
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
91
+ "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
76
92
  "@deepseek-ai/schemastery": "^3.18.4"
77
93
  },
78
94
  "devDependencies": {
79
95
  "@deepseek-ai/cordis": "^4.0.4",
80
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
81
- "@deepseek-ai/dsh-cordis-host-runner": "^0.1.0-rc.8",
82
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.8",
83
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
84
- "@deepseek-ai/dsh-scope": "^0.1.0-rc.8",
85
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
86
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.8",
87
- "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8",
88
- "@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
96
+ "@deepseek-ai/dsh-agent": "^0.2.0-rc.2",
97
+ "@deepseek-ai/dsh-cordis-host-runner": "^0.2.0-rc.2",
98
+ "@deepseek-ai/dsh-home-paths": "^0.2.0-rc.2",
99
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
100
+ "@deepseek-ai/dsh-scope": "^0.2.0-rc.2",
101
+ "@deepseek-ai/dsh-session": "^0.2.0-rc.2",
102
+ "@deepseek-ai/dsh-skill": "^0.2.0-rc.2",
103
+ "@deepseek-ai/dsh-skill-filesystem": "^0.2.0-rc.2",
89
104
  "@deepseek-ai/schemastery": "^3.18.4",
90
- "@types/node": "^26.6.2",
105
+ "@types/node": "^26.6.3",
91
106
  "typescript": "^7.0.2"
92
107
  },
93
108
  "author": {
@@ -95,7 +110,10 @@
95
110
  "url": "https://github.com/AmethystLuna"
96
111
  },
97
112
  "license": "MIT",
98
- "repository": "https://github.com/AmethystLuna/embedded-workbench",
113
+ "repository": {
114
+ "type": "git",
115
+ "url": "git+https://github.com/AmethystLuna/embedded-workbench.git"
116
+ },
99
117
  "keywords": [
100
118
  "embedded",
101
119
  "firmware",
@@ -13,7 +13,7 @@ Patterns for building embedded firmware with Keil MDK. Covers both ARM Compiler
13
13
 
14
14
  ## UV4 CLI Build (Authoritative)
15
15
 
16
- UV4.exe batch mode is the canonical build path. The Python CLI reimplementation is useful for CI but may miss include paths.
16
+ UV4.exe batch mode is the canonical build path. A CI reimplementation is possible but may miss include paths.
17
17
 
18
18
  ```powershell
19
19
  <Keil>\UV4\UV4.exe -b project.uvprojx -t TargetName -j0 -o <log_path>
@@ -145,7 +145,7 @@ A full Keil build has 5 stages:
145
145
  4. **Post-build (fromelf)** — `fromelf --bin objects/app.axf → application.bin`
146
146
  5. **Post-build (merge)** — Merge application BIN + filesystem + bootloader into flash image
147
147
 
148
- If bypassing UV4 (CI build), all 5 stages must be replicated. The Python CLI build tool handles this internally.
148
+ If bypassing UV4 (CI build), all 5 stages must be replicated explicitly.
149
149
 
150
150
  ## Merge / Packaging
151
151