dsh-embedded-workbench 0.8.10 → 0.8.12

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/index.js CHANGED
@@ -1,43 +1,45 @@
1
- /**
2
- * embedded-workbench — DeepSeek Harness native plugin for the Embedded
3
- * Workbench toolbox. Injects the session-start gate text (1% Rule, Red
4
- * Flags, Plan Verification Gate, skills roster) into the first model step
5
- * of every agent session, mirroring the SessionStart hook the Claude Code
6
- * plugin installs. The 8 skills ship in this package's `skills/` directory
7
- * and are registered at apply time into dsh's `ctx.skills` registry through
8
- * the standard filesystem provider, so they appear in every session catalog
9
- * without a manual copy step.
10
- *
11
- * Injection listens on agent/pre-step and appends the gate to the FIRST
12
- * model step that runs, once per session (guarded by the session's durable
13
- * history). Session-start inbox injection was dropped: a blank-session preset
14
- * switch (agentPreset.select -> recompose) can clear the inbox before the
15
- * first step, losing the gate for the whole session. The pre-step decision is
16
- * the durable path - anchored/bootstrap presets that strip first-step injected
17
- * reminders (skill catalog, AGENTS.md, gate plugins) simply defer this message
18
- * to the first step after their promotion, and the history guard re-injects it
19
- * there. The default gate text is the dsh-native adaptation of
20
- * `hooks/session-start-content.md`: behavior rules
21
- * (1% Rule / Red Flags / Plan Verification Gate) stay in sync, while
22
- * presentation is adapted to dsh's native skill catalog — no roster table
23
- * (the model sees skills in its catalog) and no install instructions (those
24
- * live in `.dsh/INSTALL.md`). Deployments override via Config.
25
- *
26
- * @module embedded-workbench-dsh
27
- */
28
- import { fileURLToPath } from 'node:url';
29
- import z from '@deepseek-ai/schemastery';
30
- import { createUserMessage } from '@deepseek-ai/dsh-llm';
31
- import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
32
- export const name = 'embedded-workbench';
33
- // Skills are contributed through the registry service, which dsh-base always
34
- // mounts before bundle rows such as this one apply.
35
- export const inject = ['skills'];
36
- // Absolute path of the package's shipped skills directory. `lib/index.js`
37
- // lives one level below the package root, so `../skills` from the module URL
38
- // lands on `<package>/skills` regardless of where the package was installed.
39
- const SKILLS_DIR = fileURLToPath(new URL('../skills', import.meta.url));
40
- const GATE_PLUGIN_ID = 'embedded-workbench';
1
+ /**
2
+ * embedded-workbench — DeepSeek Harness native plugin for the Embedded
3
+ * Workbench toolbox. Injects the session-start gate text (1% Rule, Red
4
+ * Flags, Plan Verification Gate, skills roster) into the first model step
5
+ * of every agent session, mirroring the SessionStart hook the Claude Code
6
+ * plugin installs. The 8 skills ship in this package's `skills/` directory
7
+ * and are registered at apply time into dsh's `ctx.skills` registry through
8
+ * the standard filesystem provider, so they appear in every session catalog
9
+ * without a manual copy step.
10
+ *
11
+ * Injection listens on agent/pre-step and appends the gate to the FIRST
12
+ * model step that runs, once per session (guarded by the session's durable
13
+ * history). Session-start inbox injection was dropped: a blank-session preset
14
+ * switch (agentPreset.select -> recompose) can clear the inbox before the
15
+ * first step, losing the gate for the whole session. The pre-step decision is
16
+ * the durable path - anchored/bootstrap presets that strip first-step injected
17
+ * reminders (skill catalog, AGENTS.md, gate plugins) simply defer this message
18
+ * to the first step after their promotion, and the history guard re-injects it
19
+ * there. The default gate text is the dsh-native adaptation of
20
+ * `hooks/session-start-content.md`: behavior rules
21
+ * (1% Rule / Red Flags / Plan Verification Gate) stay in sync, while
22
+ * presentation is adapted to dsh's native skill catalog — no roster table
23
+ * (the model sees skills in its catalog) and no install instructions (those
24
+ * live in `.dsh/INSTALL.md`). Deployments override via Config.
25
+ *
26
+ * @module embedded-workbench-dsh
27
+ */
28
+ import { fileURLToPath } from 'node:url';
29
+ import z from '@deepseek-ai/schemastery';
30
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
31
+ import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
32
+ export const name = 'embedded-workbench';
33
+ // Skills are contributed through the registry service, which dsh-base always
34
+ // mounts before bundle rows such as this one apply.
35
+ export const inject = ['skills'];
36
+ // Absolute path of the package's shipped skills directory. `lib/index.js`
37
+ // lives one level below the package root, so `../skills` from the module URL
38
+ // lands on `<package>/skills` regardless of where the package was installed.
39
+ const SKILLS_DIR = fileURLToPath(new URL('../skills', import.meta.url));
40
+ const GATE_PLUGIN_ID = 'embedded-workbench';
41
+ /** Producer-owned message source kind declared in `MessageSourceMap` above. */
42
+ const GATE_SOURCE_KIND = 'plugin:embedded-workbench';
41
43
  const DEFAULT_GATE_CONTENT = `<EXTREMELY_IMPORTANT>
42
44
  Plugin embedded-workbench is active. You have embedded C/C++ firmware development skills — names and "Use when" triggers are in your skill catalog; load them with the skill tool. No custom agents in dsh: use the native subagent tooling for parallel work.
43
45
 
@@ -62,147 +64,152 @@ Plugin embedded-workbench is active. You have embedded C/C++ firmware developmen
62
64
  To load workflows and engineering policies: load the embedded-workbench skill.
63
65
 
64
66
  **Proactive features**: When you see state machines, protocol refactoring, behavioral claims ("always"/"never"), or multi-module tasks — suggest verification (logicprobe, or the built-in fact-check fallback if logicprobe is not installed), adversarial probing, or parallel subagents BEFORE the user asks. Most users do not know these exist.
65
- </EXTREMELY_IMPORTANT>`;
66
- export const Config = z.object({
67
- enabled: z.boolean().default(true),
68
- gateContent: z.string().default(DEFAULT_GATE_CONTENT),
69
- });
70
- function gateMessage(text) {
71
- return createUserMessage({
72
- content: [{ type: 'text', text }],
73
- // `form` omitted — an undeclared context is the documented default.
74
- source: { kind: 'plugin', plugin: GATE_PLUGIN_ID },
75
- });
76
- }
77
- function readSessionEvents(session) {
78
- const source = session;
79
- if (typeof source.snapshotEvents === 'function') {
80
- const snapshot = source.snapshotEvents();
81
- if (Array.isArray(snapshot))
82
- return snapshot;
83
- }
84
- if (Array.isArray(source.events))
85
- return source.events;
86
- return [];
87
- }
88
- /**
89
- * Model-visible catalog entry (cordis_inspect_list / cordis_inspect_query):
90
- * lets the model read this plugin's runtime status without guessing. Mirrors
91
- * the registration pattern of the official dsh-tool-cordis host providers.
92
- */
93
- function inspectProvider(config) {
94
- return {
95
- manifest: {
96
- id: 'embedded-workbench',
97
- description: 'Session-start gate injection for the Embedded Workbench toolbox — folds the 1% Rule / Red Flags / Plan Verification Gate text into the first model step of every agent session.',
98
- methods: [
99
- {
100
- name: 'status',
101
- description: 'Read whether the gate injection is active and how large the injected gate text is.',
102
- inputSchema: {
103
- type: 'object',
104
- properties: {},
105
- additionalProperties: false,
106
- },
107
- outputSchema: {
108
- type: 'object',
109
- description: 'Gate-injection plugin status.',
110
- properties: {
111
- enabled: { type: 'boolean', description: 'Whether the gate folds into the first model step.' },
112
- gateContentLength: { type: 'integer', description: 'Length in characters of the injected gate text.' },
113
- },
114
- required: ['enabled', 'gateContentLength'],
115
- additionalProperties: false,
116
- },
117
- },
118
- ],
119
- },
120
- query: async (method) => {
121
- if (method === 'status') {
122
- return {
123
- enabled: config.enabled,
124
- gateContentLength: config.gateContent.length,
125
- };
126
- }
127
- return null;
128
- },
129
- };
130
- }
131
- export function apply(ctx, config) {
132
- // Catalog visibility is optional: register only when the inspect registry
133
- // service is mounted, so headless assemblies without it keep the gate
134
- // injection working. The registry may be provided AFTER this row applies
135
- // (base-bundle rows can mount later), so registration is retried on the
136
- // first agent/session-start — by then the app is fully booted.
137
- let providerRegistered = false;
138
- const registerProvider = () => {
139
- if (providerRegistered)
140
- return;
141
- const inspect = ctx.get('cordisInspect');
142
- if (inspect === undefined)
143
- return;
144
- try {
145
- ctx.effect(() => inspect.register(inspectProvider(config)), 'embedded-workbench: inspect provider');
146
- providerRegistered = true;
147
- }
148
- catch (err) {
149
- console.warn('[embedded-workbench] inspect provider registration failed', err);
150
- }
151
- };
152
- registerProvider();
153
- // Ship the bundled skills through the registry: reuse the standard
154
- // filesystem provider over this package's own `skills/` directory, so
155
- // catalog discovery, frontmatter parsing, and SKILL.md loading behave
156
- // exactly like project/user skills while the plugin stays self-contained.
157
- // Registration lands in the global registry layer (this row mounts at the
158
- // profile root), so every agent preset sees the skills. `registerProvider`
159
- // returns the effect disposer; its teardown unregisters and invalidates.
160
- ctx.skills.registerProvider((control) => {
161
- return new FileSystemSkillProvider(ctx, control, {
162
- providerName: 'embedded-workbench',
163
- includeDefaultRoots: false,
164
- customSkillDirs: [SKILLS_DIR],
165
- });
166
- });
167
- if (!config.enabled)
168
- return;
169
- // Inject the gate once per session on the FIRST model step that runs,
170
- // instead of at session-start: session-start injection lands in the agent's
171
- // inbox, which a blank-session preset switch (agentPreset.select ->
172
- // recompose) can clear before the first step - the gate would then be lost
173
- // for the whole session. The pre-step decision is the durable path a
174
- // first-step injection takes: the gate is appended to the first step's
175
- // decision and enters session history there, so every later step (and a
176
- // resume) skips it. Anchored/bootstrap presets that strip first-step
177
- // injected reminders (skill catalog, AGENTS.md, gate plugins) simply defer
178
- // this message to the first step after their promotion - the history guard
179
- // re-injects it there, so the gate still lands exactly once per session.
180
- ctx.on('agent/pre-step', async ({ agent }, next) => {
181
- const decision = await next();
182
- if (decision.kind === 'reject')
183
- return decision;
184
- registerProvider();
185
- if (gateInHistory(agent.session))
186
- return decision;
187
- return {
188
- kind: 'enter',
189
- messages: [...decision.messages, gateMessage(config.gateContent)],
190
- };
191
- });
192
- }
193
- /**
194
- * Whether the gate already entered this session's durable history. The
195
- * pre-step listener re-appends the gate until it does; once a step committed
196
- * it, every later step (and a resume of a session that kept it) skips the
197
- * injection. A session whose gate was dropped before any step ran (e.g. an
198
- * inbox cleared by a blank-session preset switch) simply re-injects on the
199
- * first step that runs.
200
- */
201
- function gateInHistory(session) {
202
- return readSessionEvents(session).some((event) => {
203
- if (event.type !== 'user/message')
204
- return false;
205
- const source = event.data?.source;
206
- return source?.kind === 'plugin' && source.plugin === GATE_PLUGIN_ID;
207
- });
208
- }
67
+ </EXTREMELY_IMPORTANT>`;
68
+ export const Config = z.object({
69
+ enabled: z.boolean().default(true),
70
+ gateContent: z.string().default(DEFAULT_GATE_CONTENT),
71
+ });
72
+ function gateMessage(text) {
73
+ return createUserMessage({
74
+ content: [{ type: 'text', text }],
75
+ // `form` omitted — an undeclared context is the documented default.
76
+ source: { kind: GATE_SOURCE_KIND },
77
+ });
78
+ }
79
+ function readSessionEvents(session) {
80
+ const source = session;
81
+ if (typeof source.snapshotEvents === 'function') {
82
+ const snapshot = source.snapshotEvents();
83
+ if (Array.isArray(snapshot))
84
+ return snapshot;
85
+ }
86
+ if (Array.isArray(source.events))
87
+ return source.events;
88
+ return [];
89
+ }
90
+ /**
91
+ * Model-visible catalog entry (cordis_inspect_list / cordis_inspect_query):
92
+ * lets the model read this plugin's runtime status without guessing. Mirrors
93
+ * the registration pattern of the official dsh-tool-cordis host providers.
94
+ */
95
+ function inspectProvider(config) {
96
+ return {
97
+ manifest: {
98
+ id: 'embedded-workbench',
99
+ description: 'Session-start gate injection for the Embedded Workbench toolbox — folds the 1% Rule / Red Flags / Plan Verification Gate text into the first model step of every agent session.',
100
+ methods: [
101
+ {
102
+ name: 'status',
103
+ description: 'Read whether the gate injection is active and how large the injected gate text is.',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {},
107
+ additionalProperties: false,
108
+ },
109
+ outputSchema: {
110
+ type: 'object',
111
+ description: 'Gate-injection plugin status.',
112
+ properties: {
113
+ enabled: { type: 'boolean', description: 'Whether the gate folds into the first model step.' },
114
+ gateContentLength: { type: 'integer', description: 'Length in characters of the injected gate text.' },
115
+ },
116
+ required: ['enabled', 'gateContentLength'],
117
+ additionalProperties: false,
118
+ },
119
+ },
120
+ ],
121
+ },
122
+ query: async (method) => {
123
+ if (method === 'status') {
124
+ return {
125
+ enabled: config.enabled,
126
+ gateContentLength: config.gateContent.length,
127
+ };
128
+ }
129
+ return null;
130
+ },
131
+ };
132
+ }
133
+ export function apply(ctx, config) {
134
+ // Catalog visibility is optional: register only when the inspect registry
135
+ // service is mounted, so headless assemblies without it keep the gate
136
+ // injection working. The registry may be provided AFTER this row applies
137
+ // (base-bundle rows can mount later), so registration is retried on the
138
+ // first agent/session-start — by then the app is fully booted.
139
+ let providerRegistered = false;
140
+ const registerProvider = () => {
141
+ if (providerRegistered)
142
+ return;
143
+ const inspect = ctx.get('cordisInspect');
144
+ if (inspect === undefined)
145
+ return;
146
+ try {
147
+ ctx.effect(() => inspect.register(inspectProvider(config)), 'embedded-workbench: inspect provider');
148
+ providerRegistered = true;
149
+ }
150
+ catch (err) {
151
+ console.warn('[embedded-workbench] inspect provider registration failed', err);
152
+ }
153
+ };
154
+ registerProvider();
155
+ // Ship the bundled skills through the registry: reuse the standard
156
+ // filesystem provider over this package's own `skills/` directory, so
157
+ // catalog discovery, frontmatter parsing, and SKILL.md loading behave
158
+ // exactly like project/user skills while the plugin stays self-contained.
159
+ // Registration lands in the global registry layer (this row mounts at the
160
+ // profile root), so every agent preset sees the skills. `registerProvider`
161
+ // returns the effect disposer; its teardown unregisters and invalidates.
162
+ ctx.skills.registerProvider((control) => {
163
+ return new FileSystemSkillProvider(ctx, control, {
164
+ providerName: 'embedded-workbench',
165
+ includeDefaultRoots: false,
166
+ customSkillDirs: [SKILLS_DIR],
167
+ });
168
+ });
169
+ if (!config.enabled)
170
+ return;
171
+ // Inject the gate once per session on the FIRST model step that runs,
172
+ // instead of at session-start: session-start injection lands in the agent's
173
+ // inbox, which a blank-session preset switch (agentPreset.select ->
174
+ // recompose) can clear before the first step - the gate would then be lost
175
+ // for the whole session. The pre-step decision is the durable path a
176
+ // first-step injection takes: the gate is appended to the first step's
177
+ // decision and enters session history there, so every later step (and a
178
+ // resume) skips it. Anchored/bootstrap presets that strip first-step
179
+ // injected reminders (skill catalog, AGENTS.md, gate plugins) simply defer
180
+ // this message to the first step after their promotion - the history guard
181
+ // re-injects it there, so the gate still lands exactly once per session.
182
+ ctx.on('agent/pre-step', async ({ agent }, next) => {
183
+ const decision = await next();
184
+ if (decision.kind === 'reject')
185
+ return decision;
186
+ registerProvider();
187
+ if (gateInHistory(agent.session))
188
+ return decision;
189
+ return {
190
+ kind: 'enter',
191
+ messages: [...decision.messages, gateMessage(config.gateContent)],
192
+ };
193
+ });
194
+ }
195
+ /**
196
+ * Whether the gate already entered this session's durable history. The
197
+ * pre-step listener re-appends the gate until it does; once a step committed
198
+ * it, every later step (and a resume of a session that kept it) skips the
199
+ * injection. A session whose gate was dropped before any step ran (e.g. an
200
+ * inbox cleared by a blank-session preset switch) simply re-injects on the
201
+ * first step that runs.
202
+ */
203
+ function gateInHistory(session) {
204
+ return readSessionEvents(session).some((event) => {
205
+ if (event.type !== 'user/message')
206
+ return false;
207
+ const source = event.data?.source;
208
+ if (source === undefined)
209
+ return false;
210
+ // The v4 producer-owned kind, plus the pre-v4 wrapper this bundle wrote
211
+ // before DSH 0.1.7-alpha.1 retired it.
212
+ return source.kind === GATE_SOURCE_KIND
213
+ || (source.kind === 'plugin' && source.plugin === GATE_PLUGIN_ID);
214
+ });
215
+ }
@@ -1,43 +1,51 @@
1
- /**
2
- * embedded-workbench — DeepSeek Harness native plugin for the Embedded
3
- * Workbench toolbox. Injects the session-start gate text (1% Rule, Red
4
- * Flags, Plan Verification Gate, skills roster) into the first model step
5
- * of every agent session, mirroring the SessionStart hook the Claude Code
6
- * plugin installs. The 8 skills ship in this package's `skills/` directory
7
- * and are registered at apply time into dsh's `ctx.skills` registry through
8
- * the standard filesystem provider, so they appear in every session catalog
9
- * without a manual copy step.
10
- *
11
- * Injection listens on agent/pre-step and appends the gate to the FIRST
12
- * model step that runs, once per session (guarded by the session's durable
13
- * history). Session-start inbox injection was dropped: a blank-session preset
14
- * switch (agentPreset.select -> recompose) can clear the inbox before the
15
- * first step, losing the gate for the whole session. The pre-step decision is
16
- * the durable path - anchored/bootstrap presets that strip first-step injected
17
- * reminders (skill catalog, AGENTS.md, gate plugins) simply defer this message
18
- * to the first step after their promotion, and the history guard re-injects it
19
- * there. The default gate text is the dsh-native adaptation of
20
- * `hooks/session-start-content.md`: behavior rules
21
- * (1% Rule / Red Flags / Plan Verification Gate) stay in sync, while
22
- * presentation is adapted to dsh's native skill catalog — no roster table
23
- * (the model sees skills in its catalog) and no install instructions (those
24
- * live in `.dsh/INSTALL.md`). Deployments override via Config.
25
- *
26
- * @module embedded-workbench-dsh
27
- */
28
- import type { Context } from '@deepseek-ai/cordis';
29
- import z from '@deepseek-ai/schemastery';
30
- export declare const name = "embedded-workbench";
31
- export declare const inject: string[];
32
- export interface Config {
33
- enabled: boolean;
34
- gateContent: string;
35
- }
36
- export declare const Config: z<Schemastery.ObjectS<{
37
- enabled: z<boolean, boolean>;
38
- gateContent: z<string, string>;
39
- }>, Schemastery.ObjectT<{
40
- enabled: z<boolean, boolean>;
41
- gateContent: z<string, string>;
42
- }>>;
43
- export declare function apply(ctx: Context, config: Config): void;
1
+ /**
2
+ * embedded-workbench — DeepSeek Harness native plugin for the Embedded
3
+ * Workbench toolbox. Injects the session-start gate text (1% Rule, Red
4
+ * Flags, Plan Verification Gate, skills roster) into the first model step
5
+ * of every agent session, mirroring the SessionStart hook the Claude Code
6
+ * plugin installs. The 8 skills ship in this package's `skills/` directory
7
+ * and are registered at apply time into dsh's `ctx.skills` registry through
8
+ * the standard filesystem provider, so they appear in every session catalog
9
+ * without a manual copy step.
10
+ *
11
+ * Injection listens on agent/pre-step and appends the gate to the FIRST
12
+ * model step that runs, once per session (guarded by the session's durable
13
+ * history). Session-start inbox injection was dropped: a blank-session preset
14
+ * switch (agentPreset.select -> recompose) can clear the inbox before the
15
+ * first step, losing the gate for the whole session. The pre-step decision is
16
+ * the durable path - anchored/bootstrap presets that strip first-step injected
17
+ * reminders (skill catalog, AGENTS.md, gate plugins) simply defer this message
18
+ * to the first step after their promotion, and the history guard re-injects it
19
+ * there. The default gate text is the dsh-native adaptation of
20
+ * `hooks/session-start-content.md`: behavior rules
21
+ * (1% Rule / Red Flags / Plan Verification Gate) stay in sync, while
22
+ * presentation is adapted to dsh's native skill catalog — no roster table
23
+ * (the model sees skills in its catalog) and no install instructions (those
24
+ * live in `.dsh/INSTALL.md`). Deployments override via Config.
25
+ *
26
+ * @module embedded-workbench-dsh
27
+ */
28
+ import type { Context } from '@deepseek-ai/cordis';
29
+ import z from '@deepseek-ai/schemastery';
30
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm';
31
+ declare module '@deepseek-ai/dsh-llm' {
32
+ interface MessageSourceMap {
33
+ 'plugin:embedded-workbench': {
34
+ kind: 'plugin:embedded-workbench';
35
+ } & ContextFormed;
36
+ }
37
+ }
38
+ export declare const name = "embedded-workbench";
39
+ export declare const inject: string[];
40
+ export interface Config {
41
+ enabled: boolean;
42
+ gateContent: string;
43
+ }
44
+ export declare const Config: z<Schemastery.ObjectS<{
45
+ enabled: z<boolean, boolean>;
46
+ gateContent: z<string, string>;
47
+ }>, Schemastery.ObjectT<{
48
+ enabled: z<boolean, boolean>;
49
+ gateContent: z<string, string>;
50
+ }>>;
51
+ export declare function apply(ctx: Context, config: Config): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-embedded-workbench",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
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 injects the session-start gate into the first model step.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -32,7 +32,7 @@
32
32
  "patch": "./cordis.patch.yml"
33
33
  },
34
34
  "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.6-alpha.1",
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.6-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1",
36
36
  "dshReleases": {
37
37
  "0.1.0-rc.7": "compatible",
38
38
  "0.1.0-rc.8": "compatible",
@@ -49,7 +49,9 @@
49
49
  "0.1.5-alpha.2": "compatible",
50
50
  "0.1.5-rc.1": "compatible",
51
51
  "0.1.5-rc.2": "compatible",
52
- "0.1.6-alpha.1": "compatible"
52
+ "0.1.6-alpha.1": "compatible",
53
+ "0.1.6-alpha.2": "compatible",
54
+ "0.1.7-alpha.1": "compatible"
53
55
  },
54
56
  "profiles": [
55
57
  "headless"
@@ -80,7 +82,7 @@
80
82
  "@deepseek-ai/dsh-skill": "^0.1.0-rc.8",
81
83
  "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8",
82
84
  "@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
83
- "@types/node": "^26.5.0",
85
+ "@types/node": "^26.6.1",
84
86
  "@deepseek-ai/schemastery": "^3.18.2",
85
87
  "typescript": "^7.0.2"
86
88
  },
package/src/index.ts CHANGED
@@ -30,10 +30,23 @@ import { fileURLToPath } from 'node:url'
30
30
  import type { Context } from '@deepseek-ai/cordis'
31
31
  import z from '@deepseek-ai/schemastery'
32
32
  import { createUserMessage } from '@deepseek-ai/dsh-llm'
33
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm'
33
34
  import type { Session, UserMessage } from '@deepseek-ai/dsh-session'
34
35
  import type { HostCordisInspectProviderRegistration } from '@deepseek-ai/dsh-cordis-host-runner'
35
36
  import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem'
36
37
 
38
+ // DSH 0.1.7-alpha.1 (session format v4) retires the shared
39
+ // `{ kind: 'plugin', plugin }` wrapper: native admission rejects it in every
40
+ // declared durable message slot, and the official v3-to-v4 migration rewrites
41
+ // those historical rows to `plugin:<name>`. Declaring the producer-owned kind
42
+ // here keeps the write path and the history guard on one identity, and still
43
+ // compiles against the earlier releases that only declare `plugin`.
44
+ declare module '@deepseek-ai/dsh-llm' {
45
+ interface MessageSourceMap {
46
+ 'plugin:embedded-workbench': { kind: 'plugin:embedded-workbench' } & ContextFormed
47
+ }
48
+ }
49
+
37
50
  export const name = 'embedded-workbench'
38
51
 
39
52
  // Skills are contributed through the registry service, which dsh-base always
@@ -47,6 +60,9 @@ const SKILLS_DIR = fileURLToPath(new URL('../skills', import.meta.url))
47
60
 
48
61
  const GATE_PLUGIN_ID = 'embedded-workbench'
49
62
 
63
+ /** Producer-owned message source kind declared in `MessageSourceMap` above. */
64
+ const GATE_SOURCE_KIND: 'plugin:embedded-workbench' = 'plugin:embedded-workbench'
65
+
50
66
  const DEFAULT_GATE_CONTENT = `<EXTREMELY_IMPORTANT>
51
67
  Plugin embedded-workbench is active. You have embedded C/C++ firmware development skills — names and "Use when" triggers are in your skill catalog; load them with the skill tool. No custom agents in dsh: use the native subagent tooling for parallel work.
52
68
 
@@ -87,7 +103,7 @@ function gateMessage(text: string): UserMessage {
87
103
  return createUserMessage({
88
104
  content: [{ type: 'text', text }],
89
105
  // `form` omitted — an undeclared context is the documented default.
90
- source: { kind: 'plugin', plugin: GATE_PLUGIN_ID },
106
+ source: { kind: GATE_SOURCE_KIND },
91
107
  })
92
108
  }
93
109
 
@@ -231,6 +247,10 @@ function gateInHistory(session: Session): boolean {
231
247
  return readSessionEvents(session).some((event) => {
232
248
  if (event.type !== 'user/message') return false
233
249
  const source = event.data?.source as { kind?: string; plugin?: string } | undefined
234
- return source?.kind === 'plugin' && source.plugin === GATE_PLUGIN_ID
250
+ if (source === undefined) return false
251
+ // The v4 producer-owned kind, plus the pre-v4 wrapper this bundle wrote
252
+ // before DSH 0.1.7-alpha.1 retired it.
253
+ return source.kind === GATE_SOURCE_KIND
254
+ || (source.kind === 'plugin' && source.plugin === GATE_PLUGIN_ID)
235
255
  })
236
256
  }