dsh-draw 0.2.12 → 0.2.15

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 (40) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/{README.es.md → README-es.md} +19 -7
  3. package/{README.hi.md → README-hi.md} +19 -7
  4. package/{README.pt.md → README-pt.md} +19 -7
  5. package/{README.zh.md → README-zh.md} +19 -7
  6. package/README.md +19 -8
  7. package/lib/client.js +69 -65
  8. package/lib/client.js.map +1 -1
  9. package/lib/index.js +49 -7
  10. package/lib/typert.host.js +1 -1
  11. package/lib/types/client/index.d.ts +12 -0
  12. package/lib/types/client/index.d.ts.map +1 -1
  13. package/lib/types/client/index.js +23 -5
  14. package/lib/types/client/remote.d.ts +110 -31
  15. package/lib/types/client/remote.d.ts.map +1 -1
  16. package/lib/types/client/styles.d.ts.map +1 -1
  17. package/lib/types/client/styles.js +24 -7
  18. package/lib/types/event-gate.d.ts +24 -2
  19. package/lib/types/event-gate.d.ts.map +1 -1
  20. package/lib/types/event-gate.js +31 -3
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/index.js +19 -2
  23. package/lib/types/session-events.d.ts.map +1 -1
  24. package/lib/types/session-events.js +9 -2
  25. package/lib/types/typert.host.d.ts +110 -31
  26. package/lib/types/typert.host.d.ts.map +1 -1
  27. package/lib/types/version.d.ts +1 -1
  28. package/lib/types/version.js +1 -1
  29. package/lib/types/wire.d.ts +220 -62
  30. package/lib/types/wire.d.ts.map +1 -1
  31. package/lib/types/wire.js +21 -35
  32. package/lib/{wire-DLW8WmDW.js → wire-BvocRNv3.js} +26 -55
  33. package/package.json +38 -36
  34. package/src/client/index.ts +20 -5
  35. package/src/client/styles.ts +24 -6
  36. package/src/event-gate.ts +45 -3
  37. package/src/index.ts +16 -2
  38. package/src/session-events.ts +9 -2
  39. package/src/version.ts +1 -1
  40. package/src/wire.ts +22 -35
@@ -5027,6 +5027,21 @@ function superRefine(fn, params) {
5027
5027
  *
5028
5028
  * @module dsh-draw/wire
5029
5029
  */
5030
+ /**
5031
+ * Strict wire codec carrying BOTH published and checkout faces: the
5032
+ * `schema` field feeds the npm-published 0.1.5-rc.2 line, `create` feeds the
5033
+ * checkout 0.1.6-alpha.1+ line (schemas materialize lazily on first use).
5034
+ * Built through a variable, so neither typecheck ruler flags the other
5035
+ * face's field as excess.
5036
+ */
5037
+ function strictWire(typeSymbol, schema) {
5038
+ return Object.freeze({
5039
+ mode: "strict",
5040
+ typeSymbol,
5041
+ schema,
5042
+ create: () => schema
5043
+ });
5044
+ }
5030
5045
  /** Strict wire schema for {@link DrawStatusSnapshot} (zod v4, both Typert faces). */
5031
5046
  const DRAW_STATUS_SCHEMA = object({
5032
5047
  pluginVersion: string(),
@@ -5171,11 +5186,7 @@ const DRAW_STATUS_DESCRIPTOR = Object.freeze({
5171
5186
  method: "status",
5172
5187
  invocation: Object.freeze({ kind: "direct" }),
5173
5188
  parameters: Object.freeze([]),
5174
- result: Object.freeze({
5175
- mode: "strict",
5176
- typeSymbol: "dsh-draw/types#DrawStatusSnapshot",
5177
- schema: DRAW_STATUS_SCHEMA
5178
- }),
5189
+ result: strictWire("dsh-draw/types#DrawStatusSnapshot", DRAW_STATUS_SCHEMA),
5179
5190
  sourceLocation: Object.freeze({
5180
5191
  file: "src/wire.ts",
5181
5192
  line: 1,
@@ -5195,17 +5206,9 @@ const DRAW_PROBE_DESCRIPTOR = Object.freeze({
5195
5206
  name: "engineId",
5196
5207
  wire: "engineId",
5197
5208
  source: "json",
5198
- codec: Object.freeze({
5199
- mode: "strict",
5200
- typeSymbol: "dsh-draw/types#EngineId",
5201
- schema: ENGINE_ID_CODEC
5202
- })
5209
+ codec: strictWire("dsh-draw/types#EngineId", ENGINE_ID_CODEC)
5203
5210
  })]),
5204
- result: Object.freeze({
5205
- mode: "strict",
5206
- typeSymbol: "dsh-draw/types#DrawProbeResult",
5207
- schema: DRAW_PROBE_SCHEMA
5208
- }),
5211
+ result: strictWire("dsh-draw/types#DrawProbeResult", DRAW_PROBE_SCHEMA),
5209
5212
  sourceLocation: Object.freeze({
5210
5213
  file: "src/wire.ts",
5211
5214
  line: 1,
@@ -5227,26 +5230,14 @@ const DRAW_SET_CREDENTIAL_DESCRIPTOR = Object.freeze({
5227
5230
  name: "engineId",
5228
5231
  wire: "engineId",
5229
5232
  source: "json",
5230
- codec: Object.freeze({
5231
- mode: "strict",
5232
- typeSymbol: "dsh-draw/types#EngineId",
5233
- schema: ENGINE_ID_CODEC
5234
- })
5233
+ codec: strictWire("dsh-draw/types#EngineId", ENGINE_ID_CODEC)
5235
5234
  }), Object.freeze({
5236
5235
  name: "value",
5237
5236
  wire: "value",
5238
5237
  source: "json",
5239
- codec: Object.freeze({
5240
- mode: "strict",
5241
- typeSymbol: "dsh-draw/types#CredentialValue",
5242
- schema: CREDENTIAL_VALUE_CODEC
5243
- })
5238
+ codec: strictWire("dsh-draw/types#CredentialValue", CREDENTIAL_VALUE_CODEC)
5244
5239
  })]),
5245
- result: Object.freeze({
5246
- mode: "strict",
5247
- typeSymbol: "dsh-draw/types#CredentialActionResult",
5248
- schema: CREDENTIAL_ACTION_SCHEMA
5249
- }),
5240
+ result: strictWire("dsh-draw/types#CredentialActionResult", CREDENTIAL_ACTION_SCHEMA),
5250
5241
  sourceLocation: Object.freeze({
5251
5242
  file: "src/wire.ts",
5252
5243
  line: 1,
@@ -5266,17 +5257,9 @@ const DRAW_UNSET_CREDENTIAL_DESCRIPTOR = Object.freeze({
5266
5257
  name: "engineId",
5267
5258
  wire: "engineId",
5268
5259
  source: "json",
5269
- codec: Object.freeze({
5270
- mode: "strict",
5271
- typeSymbol: "dsh-draw/types#EngineId",
5272
- schema: ENGINE_ID_CODEC
5273
- })
5260
+ codec: strictWire("dsh-draw/types#EngineId", ENGINE_ID_CODEC)
5274
5261
  })]),
5275
- result: Object.freeze({
5276
- mode: "strict",
5277
- typeSymbol: "dsh-draw/types#CredentialActionResult",
5278
- schema: CREDENTIAL_ACTION_SCHEMA
5279
- }),
5262
+ result: strictWire("dsh-draw/types#CredentialActionResult", CREDENTIAL_ACTION_SCHEMA),
5280
5263
  sourceLocation: Object.freeze({
5281
5264
  file: "src/wire.ts",
5282
5265
  line: 1,
@@ -5299,26 +5282,14 @@ const DRAW_REGENERATE_DESCRIPTOR = Object.freeze({
5299
5282
  name: "sessionId",
5300
5283
  wire: "sessionId",
5301
5284
  source: "json",
5302
- codec: Object.freeze({
5303
- mode: "strict",
5304
- typeSymbol: "dsh-draw/types#SessionId",
5305
- schema: SESSION_ID_CODEC
5306
- })
5285
+ codec: strictWire("dsh-draw/types#SessionId", SESSION_ID_CODEC)
5307
5286
  }), Object.freeze({
5308
5287
  name: "args",
5309
5288
  wire: "args",
5310
5289
  source: "json",
5311
- codec: Object.freeze({
5312
- mode: "strict",
5313
- typeSymbol: "dsh-draw/types#RegenerateArgs",
5314
- schema: REGENERATE_ARGS_CODEC
5315
- })
5290
+ codec: strictWire("dsh-draw/types#RegenerateArgs", REGENERATE_ARGS_CODEC)
5316
5291
  })]),
5317
- result: Object.freeze({
5318
- mode: "strict",
5319
- typeSymbol: "dsh-draw/types#DrawRegenerateResult",
5320
- schema: DRAW_REGENERATE_SCHEMA
5321
- }),
5292
+ result: strictWire("dsh-draw/types#DrawRegenerateResult", DRAW_REGENERATE_SCHEMA),
5322
5293
  sourceLocation: Object.freeze({
5323
5294
  file: "src/wire.ts",
5324
5295
  line: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-draw",
3
- "version": "0.2.12",
3
+ "version": "0.2.15",
4
4
  "description": "Unified static-image generation router for DeepSeek Harness: one image_generate tool with standard parameters, config-driven OpenAI-compatible engine routing (OpenAI Images, Zhipu CogView, and any compatible endpoint) with health-aware fallback, durable workspace attachment results, per-session quota accounting, an in-conversation result card, and a Plugins settings panel that stores API keys as credential references.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,13 +43,14 @@
43
43
  "SECURITY.md",
44
44
  "THIRD_PARTY_NOTICES.md",
45
45
  "README.md",
46
- "README.zh.md",
47
- "README.es.md",
48
- "README.pt.md",
49
- "README.hi.md",
46
+ "README-zh.md",
47
+ "README-es.md",
48
+ "README-pt.md",
49
+ "README-hi.md",
50
50
  "LICENSE"
51
51
  ],
52
52
  "dsh": {
53
+ "manifestVersion": 1,
53
54
  "bundle": {
54
55
  "patch": "./cordis.patch.yml"
55
56
  },
@@ -88,7 +89,7 @@
88
89
  "attachments:write"
89
90
  ],
90
91
  "compatibility": {
91
- "dshVersions": ["0.1.2-rc.1","0.1.5-rc.1"]
92
+ "dshVersions": ["0.1.2-rc.1","0.1.5-rc.2"]
92
93
  },
93
94
  "capability": {
94
95
  "id": "image-generate-tool",
@@ -116,24 +117,25 @@
116
117
  "text-to-image"
117
118
  ],
118
119
  "engines": {
119
- "node": "^22.19.0 || >=24.0.0"
120
+ "node": "^22.19.0 || >=24.0.0",
121
+ "dsh": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0"
120
122
  },
121
123
  "packageManager": "pnpm@11.7.0",
122
124
  "peerDependencies": {
123
125
  "@deepseek-ai/cordis": "^4.0.2",
124
- "@deepseek-ai/dsh-attachment": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
125
- "@deepseek-ai/dsh-brand": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
126
- "@deepseek-ai/dsh-credentials": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
127
- "@deepseek-ai/dsh-llm": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
128
- "@deepseek-ai/dsh-session": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
129
- "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
130
- "@deepseek-ai/dsh-typert-protocol": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
126
+ "@deepseek-ai/dsh-attachment": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
127
+ "@deepseek-ai/dsh-brand": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
128
+ "@deepseek-ai/dsh-credentials": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
129
+ "@deepseek-ai/dsh-llm": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
130
+ "@deepseek-ai/dsh-session": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
131
+ "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
132
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
131
133
  "@deepseek-ai/schemastery": "^3.18.2",
132
- "@deepseek-ai/dsh-client-connection": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
133
- "@deepseek-ai/dsh-client-locale": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
134
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
135
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0",
136
- "@deepseek-ai/dsh-client-ui-tool": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"
134
+ "@deepseek-ai/dsh-client-connection": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
135
+ "@deepseek-ai/dsh-client-locale": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
136
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
137
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0",
138
+ "@deepseek-ai/dsh-client-ui-tool": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0"
137
139
  },
138
140
  "peerDependenciesMeta": {
139
141
  "@deepseek-ai/dsh-client-connection": {
@@ -158,26 +160,26 @@
158
160
  "zod": "^4.4.3"
159
161
  },
160
162
  "devDependencies": {
161
- "@deepseek-ai/dsh-util-values": "0.1.5-rc.1",
163
+ "@deepseek-ai/dsh-util-values": "0.1.5-rc.2",
162
164
  "@deepseek-ai/cordis": "^4.0.2",
163
165
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
164
166
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
165
- "@deepseek-ai/dsh-agent": "0.1.5-rc.1",
166
- "@deepseek-ai/dsh-api-remotes": "0.1.5-rc.1",
167
- "@deepseek-ai/dsh-attachment": "0.1.5-rc.1",
168
- "@deepseek-ai/dsh-attachment-local": "0.1.5-rc.1",
169
- "@deepseek-ai/dsh-brand": "0.1.5-rc.1",
170
- "@deepseek-ai/dsh-client-connection": "0.1.5-rc.1",
171
- "@deepseek-ai/dsh-client-locale": "0.1.5-rc.1",
172
- "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
173
- "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
174
- "@deepseek-ai/dsh-client-ui-tool": "0.1.5-rc.1",
175
- "@deepseek-ai/dsh-credentials": "0.1.5-rc.1",
176
- "@deepseek-ai/dsh-credentials-local": "0.1.5-rc.1",
177
- "@deepseek-ai/dsh-llm": "0.1.5-rc.1",
178
- "@deepseek-ai/dsh-session": "0.1.5-rc.1",
179
- "@deepseek-ai/dsh-tools": "0.1.5-rc.1",
180
- "@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.1",
167
+ "@deepseek-ai/dsh-agent": "0.1.5-rc.2",
168
+ "@deepseek-ai/dsh-api-remotes": "0.1.5-rc.2",
169
+ "@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
170
+ "@deepseek-ai/dsh-attachment-local": "0.1.5-rc.2",
171
+ "@deepseek-ai/dsh-brand": "0.1.5-rc.2",
172
+ "@deepseek-ai/dsh-client-connection": "0.1.5-rc.2",
173
+ "@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
174
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
175
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
176
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.5-rc.2",
177
+ "@deepseek-ai/dsh-credentials": "0.1.5-rc.2",
178
+ "@deepseek-ai/dsh-credentials-local": "0.1.5-rc.2",
179
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
180
+ "@deepseek-ai/dsh-session": "0.1.5-rc.2",
181
+ "@deepseek-ai/dsh-tools": "0.1.5-rc.2",
182
+ "@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.2",
181
183
  "@deepseek-ai/schemastery": "^3.18.2",
182
184
  "@types/node": "^22.19.0",
183
185
  "@types/react": "18.3.31",
@@ -121,17 +121,32 @@ export async function apply(ctx: ClientContext): Promise<void> {
121
121
  }
122
122
 
123
123
  /**
124
- * Read the current session id from the sessions store face (structural:
125
- * the store shape differs across harness lines, so only the leaf is read).
124
+ * Read the current session id from the sessions store face (structural: the
125
+ * store shape differs across harness lines, so only the leaves are read).
126
+ *
127
+ * On the 0.1.6 line `SessionListState.current` no longer exists, so the card's
128
+ * regenerate belongs to the session the main view retains: pick the listed
129
+ * session whose `retainedBy.mainView` is set — the same rule the upstream
130
+ * session store applies when it resolves its main binding. Without a retained
131
+ * session the guard returns undefined and the caller reports it instead of
132
+ * regenerating against an unknown session.
126
133
  */
127
- function currentSessionId(sessions: unknown): string | undefined {
134
+ export function currentSessionId(sessions: unknown): string | undefined {
128
135
  try {
129
136
  const list = (sessions as { list?: unknown } | null)?.list
130
137
  if (typeof list !== 'object' || list === null) return undefined
131
138
  const getSnapshot = (list as { getSnapshot?: unknown }).getSnapshot
132
139
  if (typeof getSnapshot !== 'function') return undefined
133
- const current = (getSnapshot as () => { current?: unknown })().current
134
- return typeof current === 'string' ? current : undefined
140
+ const byId = (getSnapshot as () => { byId?: unknown })().byId
141
+ if (typeof byId !== 'object' || byId === null) return undefined
142
+ for (const candidate of Object.values(byId as Record<string, unknown>)) {
143
+ if (typeof candidate !== 'object' || candidate === null) continue
144
+ const retained = (candidate as { retainedBy?: { mainView?: unknown } }).retainedBy
145
+ const mainView = typeof retained?.mainView === 'number' ? retained.mainView : 0
146
+ const id = (candidate as { id?: unknown }).id
147
+ if (mainView > 0 && typeof id === 'string') return id
148
+ }
149
+ return undefined
135
150
  } catch {
136
151
  return undefined
137
152
  }
@@ -9,12 +9,22 @@
9
9
 
10
10
  const STYLE_ID = 'dsh-draw-client-styles'
11
11
 
12
+ /**
13
+ * Live installs of this stylesheet. The sheet is shared by every mount of the
14
+ * browser half, so ownership is counted: the node stays as long as any mount
15
+ * is alive, and the LAST disposer removes the node this module inserted.
16
+ * Removing it on the first unmount (or never removing it) is what produced the
17
+ * styles-lost window and the leaked node respectively.
18
+ */
19
+ let liveInstalls = 0
20
+
12
21
  /** Install the scoped stylesheet once; returns the removal disposer. */
13
22
  export function installDrawStyles(): () => void {
14
- if (document.getElementById(STYLE_ID) !== null) return () => undefined
15
- const style = document.createElement('style')
16
- style.id = STYLE_ID
17
- style.textContent = `
23
+ liveInstalls += 1
24
+ if (document.getElementById(STYLE_ID) === null) {
25
+ const style = document.createElement('style')
26
+ style.id = STYLE_ID
27
+ style.textContent = `
18
28
  .dshdraw-card { display: flex; flex-direction: column; gap: 8px; padding: 4px 0; }
19
29
  .dshdraw-grid { display: flex; flex-wrap: wrap; gap: 8px; }
20
30
  .dshdraw-figure { display: flex; flex-direction: column; gap: 4px; max-width: 320px; }
@@ -37,8 +47,16 @@ export function installDrawStyles(): () => void {
37
47
  .dshdraw-badge.warn { color: #9a6700; border-color: #9a6700; }
38
48
  .dshdraw-input { flex: 1; min-width: 180px; border: 1px solid var(--dsh-border, #d0d7de); border-radius: 6px; padding: 4px 8px; font-size: 12px; background: transparent; color: inherit; }
39
49
  `
40
- document.head.append(style)
50
+ document.head.append(style)
51
+ }
52
+ let disposed = false
41
53
  return () => {
42
- style.remove()
54
+ if (disposed) return
55
+ disposed = true
56
+ liveInstalls -= 1
57
+ if (liveInstalls <= 0) {
58
+ liveInstalls = 0
59
+ document.getElementById(STYLE_ID)?.remove()
60
+ }
43
61
  }
44
62
  }
package/src/event-gate.ts CHANGED
@@ -81,7 +81,49 @@ export function probeIgnorableAppend(): boolean {
81
81
  }
82
82
  }
83
83
 
84
- /** The gate over the host's known types plus the probed envelope support. */
85
- export function makeHostEventGate(): EventGate {
86
- return makeEventGate(KNOWN_SESSION_EVENT_TYPES, probeIgnorableAppend())
84
+ /**
85
+ * The host gate: the decision function plus the probe result it was built
86
+ * from. The result is recorded on the function instead of being thrown away,
87
+ * so status surfaces can report which regime the session is in.
88
+ */
89
+ export interface HostEventGate extends EventGate {
90
+ /** Whether the mount-time probe found `ignorable` envelope support. */
91
+ readonly ignorableAppend: boolean
92
+ /** The host-known type set the gate was built over. */
93
+ readonly knownTypes: ReadonlySet<string>
94
+ }
95
+
96
+ /**
97
+ * The gate over the host's known types plus the probed envelope support.
98
+ *
99
+ * A refusal used to be completely silent: the accounting moved to the
100
+ * in-memory fallback ledger and nothing said so. The first refusal now warns
101
+ * once through `warn` (the mount-time probe result is also readable on the
102
+ * returned function), so a degraded session is visible without spamming the
103
+ * log on every commit.
104
+ *
105
+ * @param warn - sink for the one-time degradation warning.
106
+ * @returns the gate carrying its probe result.
107
+ */
108
+ export function makeHostEventGate(warn: (message: string) => void = () => {}): HostEventGate {
109
+ const ignorableAppend = probeIgnorableAppend()
110
+ const gate = makeEventGate(KNOWN_SESSION_EVENT_TYPES, ignorableAppend)
111
+ let warned = false
112
+ const hostGate = ((type: string): EventGateDecision => {
113
+ const decision = gate(type)
114
+ if (!decision.append && !warned) {
115
+ warned = true
116
+ warn(
117
+ 'dsh-draw: this host neither knows the draw/generated event type nor honors the ignorable envelope, '
118
+ + 'so the audit event is not written; the in-memory fallback ledger keeps quota exact for this session '
119
+ + 'and resets when the session restarts',
120
+ )
121
+ }
122
+ return decision
123
+ }) as HostEventGate
124
+ Object.defineProperties(hostGate, {
125
+ ignorableAppend: { value: ignorableAppend, enumerable: true },
126
+ knownTypes: { value: KNOWN_SESSION_EVENT_TYPES, enumerable: true },
127
+ })
128
+ return hostGate
87
129
  }
package/src/index.ts CHANGED
@@ -95,12 +95,26 @@ export async function apply(ctx: Context, config: Config): Promise<void> {
95
95
  },
96
96
  attachments: () => ctx.get('attachments') as AttachmentStore | undefined,
97
97
  sessions: () => ctx.get('sessions') as SessionStore | undefined,
98
- }, { gate: makeHostEventGate(), warn: (message) => logger.warn(message) })
98
+ }, { gate: makeHostEventGate((message) => logger.warn(message)), warn: (message) => logger.warn(message) })
99
99
 
100
100
  // Service Provider — ctx.tools.register mounts the image_generate tool and
101
101
  // the `draw` Remote service; every registration is an effect on this fiber.
102
102
  ctx.effect(() => ctx.tools.register(imageGenerateTool(drawer, resolved)), 'dsh-draw: image_generate tool')
103
103
 
104
- await ctx.plugin(DrawService, { config: resolved, router, drawer, credentials: credentials() })
104
+ // A02: the mount is awaited, so hold it in an effect registered BEFORE the
105
+ // await. A disposal that lands while DrawService is still mounting then
106
+ // unwinds the child fiber through the effect instead of leaving a
107
+ // half-applied mount; the post-await fiber check keeps the success log from
108
+ // claiming a mount that was already torn down, and a real mount failure
109
+ // still propagates.
110
+ const serviceFiber = ctx.plugin(DrawService, { config: resolved, router, drawer, credentials: credentials() })
111
+ ctx.effect(() => () => { void serviceFiber.dispose() }, 'dsh-draw: draw service')
112
+ try {
113
+ await serviceFiber
114
+ } catch (error) {
115
+ if (ctx.fiber.uid === null) return // unwound by our own disposer, not a mount failure
116
+ throw error
117
+ }
118
+ if (ctx.fiber.uid === null) return
105
119
  logger.info(`image generation enabled: ${resolved.engines.map(engine => engine.id).join(', ')} (preferred ${resolved.defaultEngine})`)
106
120
  }
@@ -129,8 +129,15 @@ export function fallbackDrawGeneratedEvents(session: Session): readonly DrawGene
129
129
  * @returns the accounting events.
130
130
  */
131
131
  export function drawGeneratedEvents(session: Session): readonly import('@deepseek-ai/dsh-session').SessionEvent<'draw/generated'>[] {
132
- // alpha.5 renamed the Session.events getter to snapshotEvents(); the peer
133
- // floor (>=0.1.0-rc.8) still exposes .events, so detect at runtime.
132
+ // `Session.snapshotEvents()` is deprecated on the 0.1.6 line ("new calls are
133
+ // prohibited"), and this is an existing call. The migration target is the
134
+ // optional `sessionQuery` service (`readSession(sessionId).events`, the
135
+ // complete raw log — `readSurface` would NOT do: draw/generated is a
136
+ // non-surface audit type). Migrating it is not a local edit: quota folding
137
+ // (`quotaState` / `checkQuotaGenerations` / `checkQuotaBytes`) is synchronous
138
+ // and consumed by the tool path, the Remote status snapshot and the card, so
139
+ // the move turns that whole API async. Tracked for a dedicated card; the
140
+ // `.events` fallback below stays for the pre-alpha.5 peer floor.
134
141
  const snapshot: readonly import('@deepseek-ai/dsh-session').SessionEvent[] = typeof session.snapshotEvents === 'function'
135
142
  ? session.snapshotEvents()
136
143
  : (session as unknown as { events: readonly import('@deepseek-ai/dsh-session').SessionEvent[] }).events
package/src/version.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  */
9
9
 
10
10
  /** Plugin version; must equal the `version` field in `package.json`. */
11
- export const PLUGIN_VERSION = '0.2.12'
11
+ export const PLUGIN_VERSION = '0.2.15'
package/src/wire.ts CHANGED
@@ -11,6 +11,17 @@
11
11
 
12
12
  import { z } from 'zod'
13
13
  import type { InvocationDescriptor } from '@deepseek-ai/dsh-typert-protocol'
14
+
15
+ /**
16
+ * Strict wire codec carrying BOTH published and checkout faces: the
17
+ * `schema` field feeds the npm-published 0.1.5-rc.2 line, `create` feeds the
18
+ * checkout 0.1.6-alpha.1+ line (schemas materialize lazily on first use).
19
+ * Built through a variable, so neither typecheck ruler flags the other
20
+ * face's field as excess.
21
+ */
22
+ function strictWire<T>(typeSymbol: string, schema: T) {
23
+ return Object.freeze({ ...{ mode: 'strict' as const, typeSymbol, schema }, create: () => schema })
24
+ }
14
25
  import type { AttemptView, EngineStatus, ProbeOutcome } from './router.ts'
15
26
  import type { QuotaLimits, QuotaState } from './quota.ts'
16
27
  import type { DrawImage } from './drawer.ts'
@@ -282,11 +293,7 @@ export const DRAW_STATUS_DESCRIPTOR = Object.freeze({
282
293
  method: 'status',
283
294
  invocation: Object.freeze({ kind: 'direct' }),
284
295
  parameters: Object.freeze([]),
285
- result: Object.freeze({
286
- mode: 'strict',
287
- typeSymbol: 'dsh-draw/types#DrawStatusSnapshot',
288
- schema: DRAW_STATUS_SCHEMA,
289
- }),
296
+ result: strictWire('dsh-draw/types#DrawStatusSnapshot', DRAW_STATUS_SCHEMA),
290
297
  sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
291
298
  } as const) satisfies InvocationDescriptor
292
299
 
@@ -303,17 +310,9 @@ export const DRAW_PROBE_DESCRIPTOR = Object.freeze({
303
310
  name: 'engineId',
304
311
  wire: 'engineId',
305
312
  source: 'json',
306
- codec: Object.freeze({
307
- mode: 'strict',
308
- typeSymbol: 'dsh-draw/types#EngineId',
309
- schema: ENGINE_ID_CODEC,
310
- }),
313
+ codec: strictWire('dsh-draw/types#EngineId', ENGINE_ID_CODEC),
311
314
  } satisfies InvocationDescriptor['parameters'][number])]),
312
- result: Object.freeze({
313
- mode: 'strict',
314
- typeSymbol: 'dsh-draw/types#DrawProbeResult',
315
- schema: DRAW_PROBE_SCHEMA,
316
- }),
315
+ result: strictWire('dsh-draw/types#DrawProbeResult', DRAW_PROBE_SCHEMA),
317
316
  sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
318
317
  } as const) satisfies InvocationDescriptor
319
318
 
@@ -333,20 +332,16 @@ export const DRAW_SET_CREDENTIAL_DESCRIPTOR = Object.freeze({
333
332
  name: 'engineId',
334
333
  wire: 'engineId',
335
334
  source: 'json',
336
- codec: Object.freeze({ mode: 'strict', typeSymbol: 'dsh-draw/types#EngineId', schema: ENGINE_ID_CODEC }),
335
+ codec: strictWire('dsh-draw/types#EngineId', ENGINE_ID_CODEC),
337
336
  } satisfies InvocationDescriptor['parameters'][number]),
338
337
  Object.freeze({
339
338
  name: 'value',
340
339
  wire: 'value',
341
340
  source: 'json',
342
- codec: Object.freeze({ mode: 'strict', typeSymbol: 'dsh-draw/types#CredentialValue', schema: CREDENTIAL_VALUE_CODEC }),
341
+ codec: strictWire('dsh-draw/types#CredentialValue', CREDENTIAL_VALUE_CODEC),
343
342
  } satisfies InvocationDescriptor['parameters'][number]),
344
343
  ]),
345
- result: Object.freeze({
346
- mode: 'strict',
347
- typeSymbol: 'dsh-draw/types#CredentialActionResult',
348
- schema: CREDENTIAL_ACTION_SCHEMA,
349
- }),
344
+ result: strictWire('dsh-draw/types#CredentialActionResult', CREDENTIAL_ACTION_SCHEMA),
350
345
  sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
351
346
  } as const) satisfies InvocationDescriptor
352
347
 
@@ -363,13 +358,9 @@ export const DRAW_UNSET_CREDENTIAL_DESCRIPTOR = Object.freeze({
363
358
  name: 'engineId',
364
359
  wire: 'engineId',
365
360
  source: 'json',
366
- codec: Object.freeze({ mode: 'strict', typeSymbol: 'dsh-draw/types#EngineId', schema: ENGINE_ID_CODEC }),
361
+ codec: strictWire('dsh-draw/types#EngineId', ENGINE_ID_CODEC),
367
362
  } satisfies InvocationDescriptor['parameters'][number])]),
368
- result: Object.freeze({
369
- mode: 'strict',
370
- typeSymbol: 'dsh-draw/types#CredentialActionResult',
371
- schema: CREDENTIAL_ACTION_SCHEMA,
372
- }),
363
+ result: strictWire('dsh-draw/types#CredentialActionResult', CREDENTIAL_ACTION_SCHEMA),
373
364
  sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
374
365
  } as const) satisfies InvocationDescriptor
375
366
 
@@ -390,20 +381,16 @@ export const DRAW_REGENERATE_DESCRIPTOR = Object.freeze({
390
381
  name: 'sessionId',
391
382
  wire: 'sessionId',
392
383
  source: 'json',
393
- codec: Object.freeze({ mode: 'strict', typeSymbol: 'dsh-draw/types#SessionId', schema: SESSION_ID_CODEC }),
384
+ codec: strictWire('dsh-draw/types#SessionId', SESSION_ID_CODEC),
394
385
  } satisfies InvocationDescriptor['parameters'][number]),
395
386
  Object.freeze({
396
387
  name: 'args',
397
388
  wire: 'args',
398
389
  source: 'json',
399
- codec: Object.freeze({ mode: 'strict', typeSymbol: 'dsh-draw/types#RegenerateArgs', schema: REGENERATE_ARGS_CODEC }),
390
+ codec: strictWire('dsh-draw/types#RegenerateArgs', REGENERATE_ARGS_CODEC),
400
391
  } satisfies InvocationDescriptor['parameters'][number]),
401
392
  ]),
402
- result: Object.freeze({
403
- mode: 'strict',
404
- typeSymbol: 'dsh-draw/types#DrawRegenerateResult',
405
- schema: DRAW_REGENERATE_SCHEMA,
406
- }),
393
+ result: strictWire('dsh-draw/types#DrawRegenerateResult', DRAW_REGENERATE_SCHEMA),
407
394
  sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
408
395
  } as const) satisfies InvocationDescriptor
409
396